%w( ldap_fixtures ).each do |lib|
require "scooter/ldap/#{lib}"
end
module Scooter
module LDAP
DEFAULT_DS_PORT = 636
DEFAULT_USER_PASSWORD = 'Puppet11'
# == Quick-start guide for the impatient
# === Quick example creating an LDAPDispatcher object with a beaker config:
#
# #Example beaker configuration
# #
# #HOSTS:
# # win_2008_r2_x64:
# # roles:
# # - directory_service
# # platform: windows-2008r2-x86_64
#
# require 'scooter'
# ldapdispatcher = Scooter::LDAP::LDAPDispatcher.new(directory_service)
# # If you are not using the default static fixtures, you probably want
# # to change the credentials for your LDAP instance
# ldapdispatcher.auth(user_dn, password)
# # This is the normal method you would use to set up a standard test
# # environment, with groups of writers(poets, lyricists, novelists)
# # to populate your directory_service
# ldapdispatcher.create_default_test_groups_and_users
class LDAPDispatcher < Net::LDAP
attr_accessor :test_uid
attr_reader :ds_type, :users_dn, :groups_dn, :ds_users
# Instantiating an object of type Scooter::LDAP::LDAPDispatcher extends
# the Net::LDAP class to include helper methods to make test setup and
# cleanup more consistent and reliable for beaker tests involving the
# Puppet RBAC Service. LDAPDispatcher objects require either a Unix::Host
# or Windows::Host object passed in as a parameter, which will dictate how
# helper methods construct a test environment of groups and users.
#
# Unlike the Net::LDAP, LDAPDispatcher does test the network
# connection during initialization and raises a warning if it fails.
# @param host [Unix::Host, Windows::Host] the DS host object defined in
# your Beaker config
# @param options [hash] any params you would like to override; you are
# likely to want to do this if you are not using the static Puppet LDAP
# fixtures
def initialize(host, options={})
# All initialized LDAPDispatcher objects will have test_uids to ensure
# no collisions when creating entries in the directory services.
@test_uid = Scooter::Utilities::RandomString.generate(4)
if host.is_a? Windows::Host
@ds_type = :ad
elsif host.is_a? Unix::Host
@ds_type = :openldap
else
raise "host must be Unix::Host or Windows::Host, not #{host.class}"
end
generated_args = {}
generated_args[:host] = host.reachable_name
generated_args[:port] = DEFAULT_DS_PORT
generated_args[:encryption] = {:method => :simple_tls}
generated_args[:base] = return_default_base
generated_args.merge!(options)
super(generated_args)
# If we didn't pass in an :auth hash, generate the default settings
# using the auth method of Net::LDAP
if !options[:auth]
self.auth admin_dn, return_default_password
end
if !bind
warn "Problem binding to #{host}, #{get_operation_result}\n
username: #{admin_dn}, pw: #{return_default_password}"
end
end
def return_default_password
"Puppet11"
end
def return_default_base
'dc=delivery,dc=puppetlabs,dc=net'
end
def is_openldap?
true if @ds_type == :openldap
end
def is_windows_ad?
true if @ds_type == :ad
end
def admin_dn
if is_windows_ad?
"cn=Administrator,cn=Users,#{return_default_base}"
else
"cn=admin,#{return_default_base}"
end
end
def create_temp_ou(base_string='test_')
ou = base_string + @test_uid
dn = "ou=#{ou},#{self.base}"
attr = {:objectClass => ['top', 'organizationalUnit'],
:ou => ou}
add(:dn => dn, :attributes => attr)
if get_operation_result.code != 0
raise "OU creation failed: #{get_operation_result}, #{dn}"
end
dn
end
# This method should execute after a test's completion; the group's ou
# and users's ou will be deleted, as will any entity with those
# respective ou's in their distinguished name.
# === Example beaker teardown
#
# Example beaker teardown
#
# teardown do
# ldapdispatcher.delete_users_and_groups_organizational_units
# end
def delete_users_and_groups_organizational_units
delete_all_dn_entries(@groups_dn)
delete_all_dn_entries(@users_dn)
end
def delete_all_dn_entries(dn)
entries = search(:base => dn, :attributes => ['dn'])
entries.each do |entry|
delete :dn => entry.dn
end
#This needs to be repeated because it may have failed deleting a group
#that still had users associated.
entries.each do |entry|
delete :dn => entry.dn
end
#This request should return nil; all entities with the dn provided
#should now be deleted.
entries = search(:base => dn, :attributes => ['dn'])
if entries != nil
raise "Problem deleting all entries for this dn: #{dn}"
end
end
def create_ds_user(attributes, users_dn=self.users_dn)
default_attributes = {:objectClass => ['top',
'person',
'organizationalPerson',
'inetOrgPerson']}
if is_windows_ad?
default_attributes[:userAccountControl] = ['544']
end
default_attributes.merge!(attributes)
add(:dn => "cn=#{default_attributes[:cn]},#{users_dn}",
:attributes => default_attributes)
if get_operation_result.code != 0
raise "Creating user failed: #{get_operation_result}\n
#{default_attributes}"
end
end
def create_ds_group(attributes, groups_dn=self.groups_dn)
#When Openldap, you must specify :member entries in the attributes
default_attributes = {:objectClass => ["top", "groupOfUniqueNames"]}
if is_windows_ad?
default_attributes[:objectClass] = ["top", "group"]
end
default_attributes.merge!(attributes)
add(:dn => "cn=#{default_attributes[:cn]},#{groups_dn}",
:attributes => default_attributes)
if get_operation_result.code != 0
raise "Creating group failed: #{get_operation_result},\n
#{default_attributes}"
end
end
def create_ou_for_users_and_groups
@users_dn = create_temp_ou('users')
@groups_dn = create_temp_ou('groups')
end
# This is the primary method most tests will use. It creates two
# organizational units, or ou's, to base all your testing around. There is
# one ou for groups and one for users. Most testing can be covered by
# simply running the method create_default_test_groups_and_users.
def create_default_test_groups_and_users
create_ou_for_users_and_groups
create_default_users
if is_windows_ad?
create_windows_ad_default_users_and_test_groups
elsif is_openldap?
create_openldap_default_users_and_test_groups
end
end
def create_default_users
users = Scooter::LDAP::LDAPFixtures.users(@test_uid)
users.each do |name, hash|
create_ds_user(hash)
update_user_password("CN=#{hash[:cn]},#{users_dn}", DEFAULT_USER_PASSWORD)
hash[:password] = DEFAULT_USER_PASSWORD
end
@ds_users = users
end
#This is used to encode passwords for Windows AD
#See URL: http://msdn.microsoft.com/en-us/library/cc223248.aspx
def str_to_unicode_pwd(str) #:nodoc:
('"' + str + '"').encode("utf-16le").force_encoding("utf-8")
end
def update_user_password(user_dn, password) #:nodoc:
if is_windows_ad?
password = str_to_unicode_pwd(password)
ops = [[:replace, :unicodePwd, password]]
else
ops = [[:replace, :userPassword, password]]
end
modify :dn => user_dn, :operations => ops
if get_operation_result.code != 0
raise "Updating password failed: #{get_operation_result}\n
#{ops}"
end
end
private
def create_openldap_default_users_and_test_groups #:nodoc:
#add novelists
novelists =["cn=#{ds_users['arthur'][:cn]},#{users_dn}",
"cn=#{ds_users['howard'][:cn]},#{users_dn}",
"cn=#{ds_users['oscar'][:cn]},#{users_dn}"]
create_ds_group({ :cn => "novelists#{@test_uid}",
:uniqueMember => novelists })
#add poets
poets = ["cn=#{ds_users['sylvia'][:cn]},#{users_dn}",
"cn=#{ds_users['jorge'][:cn]},#{users_dn}",
"cn=#{ds_users['oscar'][:cn]},#{users_dn}"]
create_ds_group({ :cn => "poets#{@test_uid}",
:uniqueMember => poets })
#add lyricists
lyricists = ["cn=#{ds_users['stephen'][:cn]},#{users_dn}"]
create_ds_group({ :cn => "lyricists#{@test_uid}",
:uniqueMember => lyricists })
#add writers
writers = ["cn=#{ds_users['guillermo'][:cn]},#{users_dn}",
"cn=novelists#{test_uid},#{groups_dn}",
"cn=poets#{test_uid},#{groups_dn}",
"cn=lyricists#{test_uid},#{groups_dn}"]
create_ds_group({ :cn => "writers#{@test_uid}",
:uniqueMember => writers })
end
def create_windows_ad_default_users_and_test_groups #:nodoc:
writers_cn = "writers#{@test_uid}"
poets_cn = "poets#{@test_uid}"
novelists_cn = "novelists#{@test_uid}"
lyricists_cn = "lyricists#{@test_uid}"
create_ds_group(:cn => lyricists_cn)
create_ds_group(:cn => novelists_cn)
create_ds_group(:cn => poets_cn)
create_ds_group(:cn => writers_cn)
add_attribute("cn=#{writers_cn},#{groups_dn}",
:member,
"cn=#{lyricists_cn},#{groups_dn}")
add_attribute("cn=#{writers_cn},#{groups_dn}",
:member,
"cn=#{poets_cn},#{groups_dn}")
add_attribute("cn=#{writers_cn},#{groups_dn}",
:member,
"cn=#{novelists_cn},#{groups_dn}")
add_attribute("cn=#{novelists_cn},#{groups_dn}",
:member,
"cn=#{ds_users['howard'][:cn]},#{users_dn}")
add_attribute("cn=#{novelists_cn},#{groups_dn}",
:member,
"cn=#{ds_users['arthur'][:cn]},#{users_dn}")
add_attribute("cn=#{novelists_cn},#{groups_dn}",
:member,
"cn=#{ds_users['oscar'][:cn]},#{users_dn}")
add_attribute("cn=#{poets_cn},#{groups_dn}",
:member,
"cn=#{ds_users['sylvia'][:cn]},#{users_dn}")
add_attribute("cn=#{poets_cn},#{groups_dn}",
:member,
"cn=#{ds_users['jorge'][:cn]},#{users_dn}")
add_attribute("cn=#{poets_cn},#{groups_dn}",
:member,
"cn=#{ds_users['oscar'][:cn]},#{users_dn}")
add_attribute("cn=#{writers_cn},#{groups_dn}",
:member,
"cn=#{ds_users['guillermo'][:cn]},#{users_dn}")
add_attribute("cn=#{lyricists_cn},#{groups_dn}",
:member,
"cn=#{ds_users['stephen'][:cn]},#{users_dn}")
end
end
end
end