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.

If you want to test your configuration file:

require 'net/ldap'
require 'ladle'
 
$ladle ||= Ladle::Server.new(
  :ldif => "spec/ldap/test_users.ldif",
  :domain => "dc=example,dc=org",
  :quiet => true
)
$ladle.start
 
ldap = Net::LDAP.new(host: 'localhost',
    :port => 3897,
)
filter = Net::LDAP::Filter.eq('mail', 'eadmin@example.com') # or ('uid', 'eadmin') 
 
ldap.search(:base => 'ou=people,dc=example,dc=org', :filter => filter) do |entry|
  ldap.auth(entry.dn, 'b44b44bl@cksh33p!') # or whatever your password is
 
  entry.each do |attribute, values|
    puts "   #{attribute}:"
    values.each do |value|
      puts "      --->#{value}"
    end
  end
end

Kentucky Lottery Pick 3 Expected Payout

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.

Also, removed the other two "fails" from the code. Not necessary and will result in a "Failed to Login" message for too many other Devise-related Unauthorized events.

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
        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 < ActiveRecord::Base
  devise :database_authenticatable,
         :recoverable, :trackable, :secure_validatable,
         :timeoutable,
         :password_expirable,
         :password_archivable,
         :lockable,
         :authentication_keys => [: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.

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.