Hooking in an LDAP Test Server to Cucumber Tests

I managed to get a custom Devise strategy with LDAP working, but had no clear way of automating tests. I wanted to validate if I still had to keep the password fresh in the database, and needed to be able to write scenarios around that in case someone attempted to refactor out the code.

After trying to incorporate the spec strategy used in the development devise_ldap_authenticatable and failing, I found a ruby wrapper of ApacheDS called ladle that looked like it would serve my purposes.

I included in gem in my test group in my Gemfile:

  gem 'ladle'

At the top of my features/env.rb file for configuring cucumber, I turned off admin binding (wanted the connection as simple as possible):

::Devise.ldap_use_admin_to_bind = false

I then created an @ldap tag for my LDAP-dependent features than would start and stop the LDAP server in those instances. (Again, in my features/env.rb… probably need to clean that up.)

Around('@ldap') do |scenario, block|
  $ladle ||= Ladle::Server.new(
    :ldif => "spec/ldap/test_users.ldif",
    :domain => "dc=example,dc=org",
    :quiet => true
  )
  $ladle.start
  block.call
  $ladle.stop
end

I then created an the spec/ldap/test_users.ldif (from following the example in the ladle project).

version: 1
 
dn: ou=people,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: people
 
dn: uid=eadmin,ou=people,dc=example,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Example Admin
sn: Admin
givenName: Example
mail: eadmin@example.com
uid: eadmin
# Password is "b44b44bl@cksh33p!"
userpassword: {SHA}Aedq5WHQSxglvJSfpX0kgdGRdHk=

I generated the password with:

  slappasswd -h {SHA} -s b44b44bl@cksh33p!

One stupid mistake that I did in the process was kicking off two Ladle servers (with slightly different parameters). In one iteration, I couldn’t bind to the user. Another, the server using the test file failed to start. Be aware that Ladle will run happily with default parameters, but that they won’t be much use to you.

Kentucky Lottery Pick 3 Expected Payout Weirdness

The expected payouts for the Kentucky Lottery Pick 3 game have an interesting idiosyncrasy. Playing the special “Straight Box” play pays out better than a separate 50¢ straight and 50¢ box play.

All other plays average 60¢ per dollar played, but the straight box seems to be 65¢ and 70¢, depending on the numbers played. Maybe my spreadsheet is wrong somewhere in here:

Kentucky Lottery Pick 3 Payouts and Odds

devise_ldap_authentication for your domain email on top of database_authenticatable

I have a devise user model named LoginUser whose authentication key is :login. I want normal users of the system to be database_authenticatable.

However, I want to be able to authenticate previously added users via internal LDAP. Furthermore, I didn’t want the underlying database_authenticatable password to be used or to expire on me (also using devise_security_extensions). Most of the work is in the LocalOverride custom strategy’s authenticate! method, with a few other hooks (such as default strategy added to devise.yml).

Update:

To allow all other strategies to be used, but still trap our domains for one-off LDAP auth, I added devise :ldap_authenticatable to a singleton class inherited from the user loaded by the custom strategy.

In config/initializers/local_override.rb:

module Devise
  module Strategies
    class LocalOverride < Authenticatable
      def valid?
        true
      end
 
      def authenticate!
        if params[:login_user]
          user = LoginUser.find_by_login(params[:login_user][:login])
          # trap our domain only
          if params[:login_user][:login] =~ /@example.com/
            # fail! halts the authentication chain completely
            return fail! unless ::Devise::LDAP::Adapter.valid_login?(params[:login_user][:login])
            class << user
              # make use of ldap_authenticatable for custom strategy only
              devise :ldap_authenticatable
            end
            return fail! unless user.valid_ldap_authentication?(params[:login_user][:password])
            # use the after_ldap_authentication hook
            user.after_ldap_authentication
            return success!(user)
          end
          fail
        else
          fail
        end
      end
    end
  end
end
 
Warden::Strategies.add(:local_override, Devise::Strategies::LocalOverride)

In config/initializers/devise.rb:

  # use local_override as default strategy
  config.warden do |manager|
    manager.default_strategies(:scope => :login_user).unshift :local_override
  end

In config/models/login_user.rb:

class LoginUser &lt; ActiveRecord::Base
  devise :database_authenticatable,
         :recoverable, :trackable, :secure_validatable,
         :timeoutable,
         :password_expirable,
         :password_archivable,
         :lockable,
         :authentication_keys =&gt; [:login]
#
#
 
  def after_ldap_authentication
    # force fresh password every log in
    self.password = self.password_confirmation = Random.new.bytes(47)
    self.save
  end
 
#
#
end
defaults:  &defaults
  host: our.ldap
  port: 636
  attribute: mail
  base: dc=IDENT,o=Orgname
  admin_password: adminpassw0rd
  ssl: sslmethod

See local_override.rb for original tip that got me there.

IMG_4542.JPG

One Year In: Prius 2014 Gas Mileage + Thule Carrier + Road Trip

Prius with Thule Cargo Box

The Prius has definitely been a very geeky car to play around with and figure out. It’s been fun to see how various environments and driving styles impact it.

My initial driving in the summer of last year yielded about 42.9 MPG. This was in stop-and-go traffic, but often stopped long enough that the engine would have to kick in to power the A/C while idle. I was able to optimize to about 45 MPG with 72ºF+ auto A/C and on a route that allowed for more constant movement.

The first road trip (1 adult, 2 kids) down I-65 to Florida in the summer yielded tanks MPG of 39-42 MPG. Another road trip to South Carolina with two adults and two kids yielded 39.6 MPG for the trip.

The brutal winter earlier this year was pretty frustrating on gas mileage. The only driving done in the car was done with the engine running constantly just to try and warm up, and I discovered that there’s no good way to get a Prius warm enough to melt the ice on your windshield unless you edge it into the sunlight. There were a few tanks that I got 33 MPG on.

We eventually installed Thule Aeroblades on our car which allowed mounting of a Thule Cargo Box. The car got under 40 MPG with the cargo box mounted, but did about 35.8 MPG on the highway drive down. The best tank MPG that I got on our Camry on 100% interstate was still under 35 MPG.

Of course, the biggest gas mileage benefit of the Prius was when we were driving 4-8 miles back and forth on St. George Island at 25-35 MPH. I’d occasionally make trips without the engine even turning on, and the average trip MPG was over 50 MPG.

Almost a year in, this car has been fun to play around with and a great road trip car (especially with the roof rack). The biggest surprise is how it has fascinated my geek side.

No Bikes, No Ice Cream, No Bacon, No Ice, No Signal, No Laundry – St George Island Spring Break

2015-04-08 13.26.00
View from the bay side of SGI.

Based on the fact that people seem to love to go to St. George Island, Florida with their dogs, and the $24/night camping available at Dr. Julian G. Bruce St. George Island State Park, trying a new place for Spring Break seemed to be worth the risk. Unfortunately, our risk calculation was poorly researched from the start. Our normal beach destination, Santa Rosa Beach, FL, is only a 9 1/2 hour drive from Louisville, KY vs. over 11 hours to SGI. Fortunately, we broke up the drive both ways with overnight stops, but that seemed to just add to the perceived travel time.

I’d imagine part of the appeal of SGI is its relative remoteness. The state park is on the eastern tip of the island as well, which makes it even more remote. The campsites are 4 miles from the entrance to the park (with official speed limits of 15-25 MPH). It’s another 2-3 miles at 35 MPH from the state park to the first commercial businesses. Camping with limited food supplies meant that eating often required a 30+ minute round trip. The state park didn’t have laundry facilities as our normal Spring Break destination state park does, and the closest laundromat is back across the bridge (probably 20+ minutes away).

Somehow, the squirrels and raccoons on a island that is only connected to the mainland by a 6 mile long bridge are more numerous and aggressive than the animals in the park at Santa Rosa Beach, Florida, just 2-3 hours west on US-98… it only required a few minutes of someone carelessly leaving a campsite unattended for food to be devoured by squirrels in the day and raccoons at night. Of course, it’s not that surprising for experienced campers that animals will target your food, but the efficiency and skill at which they did it *was* surprising. One such casualty was a pound of bacon left in a cooler. The mosquitoes were also out in way more force than other camping trips to the panhandle.

The business owners on SGI expressed surprise that last week was the busiest week they’ve ever experienced. We weren’t able to try to rent bikes until Monday morning, and by then, there were no adult bikes available on the island. We ended up with with 2 20″ bikes and a beat up adult tricycle with a basket for $125. Not really that bad a deal, but one of the bikes went unused most of the week because of its size.

Costs

Restaurants seemed to be a bit pricey relative to our regular stops of Edisto, Santa Rosa, and Outer Banks. Taxes are 7% SGI tax + 2% local option, which further inflates your bills.

Truly Disconnected and the Restaurants

Wireless signal was non-existent for us. Looking at coverage maps, it appears that the magic carrier down on St. George Island is Verizon.

Harry A’s and Subway were staples of the week. Harry A’s is moderately priced, but they have solid wifi, which was necessary because our T-Mobile phones were rarely capable of even texting. Subway actually advertises “We have wifi”. Obviously, it’s a pervasive problem.

The Blue Parrot Paradise Cafe was good and is on the Gulf side which was good for the view, but horrible for cell phone reception and they don’t have wi-fi.

Eddie Teach’s was a pretty miserable experience on a Wednesday night. They ended up being crowded, and then we made the mistake of ordering a pizza for my daughter. The pizza clearly comes from elsewhere–pretty sure it was from the gas station in Eastpoint, 6 miles away. When the pizza came back, the order was wrong, so they reordered. We had a dispute with the owner about whether we should be paying for a pizza that was 30 minutes later than the rest of the order, etc. I won’t be going back there again if I end up on SGI, and I’m definitely not ordering the pizza. My wife got an order of nachos for $10.99 that was pretty mediocre, too.

It seems that the businesses run into supply issues due their remoteness. Subway ran out of bacon. The ice cream shop next to the beach store had a few pathetic looking tubs of partially melted ice cream where there should have been 30 selections or so. The state park ran out of ice for sale on our second to last day (Thursday) with no apparent expectation of it being replenished. Edisto Island in South Carolina seems similarly isolated, but doesn’t seem to have the same supply problem… yet.

Two pleasant surprises of our trip

  • The Apalachicola Bay Aquatic Preserve has several small aquariums showcasing area aquatic life and other exhibits. It’s either brand new or kept in pristine condition.
  • Our trip to Apalachicola, which gave me the opportunity to stop in at the Oyster City Brewing Company (no food, just tap room) and then have incredibly inexpensive dinner at The Hole in the Wall. I would definitely spend more time in Apalachicola next time… it has so much old city charm. Unfortunately, many places seem to close early there.

Trails and Activities

I didn’t bother with my own bike (considering there weren’t any available), but if I had, I would have ridden down the multi-use path that goes down the main road across the island. It goes on for miles (13+ if I recall correctly), but it wasn’t scenic enough for my interests, and it was too far away (4 miles) to run to. Instead, I first explored the limited access park road beyond the last beach access. It’s mostly sand and gravel, and wasn’t suitable for either trail or standard running shoes. More importantly, it had no shade to speak of, so I ran 3 of the 4.2 miles out, and ended up overheating from the sun and walking back.

I next tried the path to the primitive campsite (and Gap Point). 2.5 miles (one way) of about 50% dunes with slightly more shade than the other paths. It was more interesting, but there wasn’t the usual mental stimulation of a trail run, as there’s pretty much only one direction to go in. On my way back, I tried the East Slough trail that goes over wooden bridges. I was pressed for time and baking in the sun so I cut that exploration short.

Final Thoughts

Maybe it was more humid than normal this year. Maybe SGI was more crowded this year.

Experiences with Dropbox as iPhoto Library Backup

Background:

The renewal on my annual Mozy Backup subscription was due, and I decided that I wanted to consolidate backup solutions. Dropbox had (recently?) upped their Pro plan to 1TB, so there was plenty of room to back up a 100GB iPhoto library.

I made the switch on Wednesday of this week, dragging the iPhoto library in Photos over to the Dropbox folder (do this with iPhoto closed and then double-click once moved to let iPhoto figure out the new location.) I did a selective sync to another computer that was lacking in hard drive space so that the Dropbox sync wouldn’t eat up all the space.

Finder claims there are about 98,000 in the library. Dropbox was indexing about 360,000. I haven’t dug into the discrepancy, but I’m guessing hidden files aren’t in the Finder count. 

 Setup:

MacBook Pro mid-2009 model, 8 GB RAM, 1 TB 5400 RPM HD, 50/10 Mbps Cable Internet service through TWC. 

 Tweaks:

Default setting for Dropbox upload is to “Limit automatically” (as shown below) to minimize disruption to other Internet activities. I had to change that to “Don’t Limit”.

Dropbox Network Settings

As of Saturday (3 days later):

I’ve also done a disk clone of the of the 1TB and gotten caught up on Time Machine backups, but three days later, Dropbox has indexed half of the files and uploaded 10GB of data. 

 As of Monday (5 days later):

Dropbox is down to only indexing its last 18,000 files (5.6%) and has uploaded roughly 85 GB of data.

As of Wednesday (7 days later): Dropbox appears to have backed up 160 GB of data, but it also appears to have crashed at some point along the way as well.

devise_security_extension undefined method authenticate for nil:NilClass on Rspec Tests for Controller That Skips Authentication

After installing the Devise Security Extension to add password expiration to my Devise models, I started getting the following error on an RSpec test for a controller that does not perform authentication of the user:

     Failure/Error: get :index
     NoMethodError:
       undefined method `authenticate?' for nil:NilClass

After a bit of digging, I found that the helpers.rb in the gem includes an additional before_filter that needs to be skipped:

module DeviseSecurityExtension
  module Controllers
    module Helpers
      extend ActiveSupport::Concern
 
      included do
        before_filter :handle_password_change
      end

So while I’m skipping authenticate_user! in my controller, I still needed an additional:

  skip_before_filter :handle_password_change

Interestingly enough, the controller itself doesn’t break, just the tests. The downside is that I’m referencing two different Devise filters/actions just to not use them.

How safe is an MD5 hash of a plain password?

First of all, I hope that you’ve moved beyond MD5 hashes and hashing passwords by themselves, adding salts, etc., but I do recall systems in which an MD5 hash of a password by itself was “good enough”.

You can look up some md5 hashes on this md5 cracker page. I found many two word combinations that were crackable.

You can play with generating md5 hashes of questionable passwords (such as your name and p@ssw0rd) with this md5 Hash Generator

Ruby Keyword Argument Implementation Differences Between 2.1 and 2.2

I’m currently reading through Ruby Under a Microscope: An Illustrated Guide to Ruby Internals, and when I tried to test the “hidden hash” in ruby 2.2, I got different results from the book.

In Ruby 2.2, it appears that the default arguments are used as one expects, without regard to the Hash monkey patch:

### CODE ###
class Hash
  def key?(val)
    false
  end
end
 
def blah(a: 2, b: 5)
  puts a + b
end
blah
blah(a:3)
blah(b:3)
blah(a:2000, b:2000)
 
### DISASSEMBLY ###
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putspecialobject 3
0004 putnil
0005 defineclass      :Hash, <class:Hash>, 0
0009 pop
0010 trace            1                                               (   7)
0012 putspecialobject 1
0014 putspecialobject 2
0016 putobject        :blah
0018 putiseq          blah
0020 opt_send_without_block <callinfo!mid:core#define_method, argc:3, ARGS_SIMPLE>
0022 pop
0023 trace            1                                               (  10)
0025 putself
0026 opt_send_without_block <callinfo!mid:blah, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0028 pop
0029 trace            1                                               (  11)
0031 putself
0032 putobject        3
0034 opt_send_without_block <callinfo!mid:blah, argc:1, kw:1, FCALL>
0036 pop
0037 trace            1                                               (  12)
0039 putself
0040 putobject        3
0042 opt_send_without_block <callinfo!mid:blah, argc:1, kw:1, FCALL>
0044 pop
0045 trace            1                                               (  13)
0047 putself
0048 putobject        2000
0050 putobject        2000
0052 opt_send_without_block <callinfo!mid:blah, argc:2, kw:2, FCALL>
0054 leave
== disasm: <RubyVM::InstructionSequence:<class:Hash>@<compiled>>========
0000 trace            2                                               (   1)
0002 trace            1                                               (   2)
0004 putspecialobject 1
0006 putspecialobject 2
0008 putobject        :key?
0010 putiseq          key?
0012 opt_send_without_block <callinfo!mid:core#define_method, argc:3, ARGS_SIMPLE>
0014 trace            4                                               (   5)
0016 leave                                                            (   2)
== disasm: <RubyVM::InstructionSequence:key?@<compiled>>================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] val<Arg>
0000 trace            8                                               (   2)
0002 trace            1                                               (   3)
0004 putobject        false
0006 trace            16                                              (   4)
0008 leave                                                            (   3)
== disasm: <RubyVM::InstructionSequence:blah@<compiled>>================
local table (size: 4, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: 2@0, kwrest: -1])
[ 4] a          [ 3] b          [ 2] ?
0000 trace            8                                               (   7)
0002 trace            1                                               (   8)
0004 putself
0005 getlocal_OP__WC__0 4
0007 getlocal_OP__WC__0 3
0009 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>
0011 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0013 trace            16                                              (   9)
0015 leave                                                            (   8)
 
### RESULTS ###
7
8
5
4000

If I reset back to ruby 2.1, I get the unconditional usage of the default arguments:

### CODE ###
class Hash
  def key?(val)
    false
  end
end
 
def blah(a: 2, b: 5)
  puts a + b
end
blah
blah(a:3)
blah(b:3)
blah(a:2000, b:2000)
 
### DISASSEMBLY ###
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putspecialobject 3
0004 putnil
0005 defineclass      :Hash, <class:Hash>, 0
0009 pop
0010 trace            1                                               (   7)
0012 putspecialobject 1
0014 putspecialobject 2
0016 putobject        :blah
0018 putiseq          blah
0020 opt_send_simple  <callinfo!mid:core#define_method, argc:3, ARGS_SKIP>
0022 pop
0023 trace            1                                               (  10)
0025 putself
0026 opt_send_simple  <callinfo!mid:blah, argc:0, FCALL|VCALL|ARGS_SKIP>
0028 pop
0029 trace            1                                               (  11)
0031 putself
0032 putspecialobject 1
0034 putobject        [:a, 3]
0036 opt_send_simple  <callinfo!mid:core#hash_from_ary, argc:1, ARGS_SKIP>
0038 opt_send_simple  <callinfo!mid:blah, argc:1, FCALL|ARGS_SKIP>
0040 pop
0041 trace            1                                               (  12)
0043 putself
0044 putspecialobject 1
0046 putobject        [:b, 3]
0048 opt_send_simple  <callinfo!mid:core#hash_from_ary, argc:1, ARGS_SKIP>
0050 opt_send_simple  <callinfo!mid:blah, argc:1, FCALL|ARGS_SKIP>
0052 pop
0053 trace            1                                               (  13)
0055 putself
0056 putspecialobject 1
0058 putobject        [:a, 2000, :b, 2000]
0060 opt_send_simple  <callinfo!mid:core#hash_from_ary, argc:1, ARGS_SKIP>
0062 opt_send_simple  <callinfo!mid:blah, argc:1, FCALL|ARGS_SKIP>
0064 leave
== disasm: <RubyVM::InstructionSequence:<class:Hash>@<compiled>>========
0000 trace            2                                               (   1)
0002 trace            1                                               (   2)
0004 putspecialobject 1
0006 putspecialobject 2
0008 putobject        :key?
0010 putiseq          key?
0012 opt_send_simple  <callinfo!mid:core#define_method, argc:3, ARGS_SKIP>
0014 trace            4                                               (   5)
0016 leave                                                            (   2)
== disasm: <RubyVM::InstructionSequence:key?@<compiled>>================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] val<Arg>
0000 trace            8                                               (   2)
0002 trace            1                                               (   3)
0004 putobject        false
0006 trace            16                                              (   4)
0008 leave                                                            (   3)
== disasm: <RubyVM::InstructionSequence:blah@<compiled>>================
local table (size: 4, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 2@2] s0)
[ 4] a          [ 3] b          [ 2] ?
0000 getlocal_OP__WC__0 2                                             (   7)
0002 dup
0003 putobject        :a
0005 opt_send_simple  <callinfo!mid:key?, argc:1, ARGS_SKIP>
0007 branchunless     18
0009 dup
0010 putobject        :a
0012 opt_send_simple  <callinfo!mid:delete, argc:1, ARGS_SKIP>
0014 setlocal_OP__WC__0 4
0016 jump             22
0018 putobject        2
0020 setlocal_OP__WC__0 4
0022 dup
0023 putobject        :b
0025 opt_send_simple  <callinfo!mid:key?, argc:1, ARGS_SKIP>
0027 branchunless     38
0029 dup
0030 putobject        :b
0032 opt_send_simple  <callinfo!mid:delete, argc:1, ARGS_SKIP>
0034 setlocal_OP__WC__0 3
0036 jump             42
0038 putobject        5
0040 setlocal_OP__WC__0 3
0042 pop
0043 trace            8
0045 trace            1                                               (   8)
0047 putself
0048 getlocal_OP__WC__0 4
0050 getlocal_OP__WC__0 3
0052 opt_plus         <callinfo!mid:+, argc:1, ARGS_SKIP>
0054 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0056 trace            16                                              (   9)
0058 leave                                                            (   8)
 
### RESULTS ###
7
7
7
7

Rails AntiPatterns Review/Summary

I’ve had the Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) book on my Safari Books Online bookshelf for almost a year now, and finally started reading through it. It was “updated for Rails 3″, and there doesn’t appear to be a newer version, so I was a bit skeptical about how much I’d get out of the book.

This book has some overlap with Ruby Science, but it is more Rails-centric in its approach. Going through the book I found quite a few topics that I’ve not really read in-depth on in any other book or come across in a couple years of Rails development experience.

  • Use of ActiveSupport::Concern included for specifying validations as part of a module.
  • Discussion of Rails plugins (which later were mentioned as falling out of favor)
  • serialize :value, Hash vs checkbox/boolean model
  • How Rails helps in the building of Semantic HTML
  • Gems Clearance and AuthLogic as simple authentication gems.
  • MVP pattern and ActivePresenter (looks like Rails 3 was the end?)
  • Emphasis on controller as a representation of a resource
  • Rails responders for changing respond_to to respond_with(object)
  • rescue *EXPECTED_EXCEPTIONS (EXPECTED_EXPECTIONS as a constant array) to whitelist exceptions that are anticipated.
  • Mechanize and RestClient as automated interface gems with other sites.
  • render :status => :unprocessable_entity (or :created and other symbols that represent HTTP status codes)
  • Strategies for testing rake tasks, including FileUtils::NoWrite
  • sqlite3 “:memory:” database for gem testing
  • Thoughts on when to add DB indexes
  • AR association methods count vs. length vs. size and their behavior and resource usage.
  • .flatten() as a code smell for ruby code that should be represented as SQL.
  • Run rake db:migrate && rake db:migrate:redo for all migrations created.
  • Don’t go against AR bias against DB constraints except for NULL & default values.
  • Never fail quietly, whether ignoring return values or overzealously catching all exceptions.
  • rescue_from on controller to redirect specific exceptions.

I think the general concepts covered provided valuable background and food for thought even if not all the solutions are still the same. I don’t regret the help examining current practices in my daily Rails development.