lib/active_ldap.rb in activeldap-1.2.4 vs lib/active_ldap.rb in activeldap-3.1.0

- old
+ new

@@ -1,927 +1,23 @@ -# = ActiveLdap -# -# Copyright (C) 2004,2005 Will Drewry mailto:will@alum.bu.edu -# Copyright (C) 2006-2010 Kouhei Sutou <kou@clear-code.com> -# -# == Introduction -# -# ActiveLdap is a novel way of interacting with LDAP. Most interaction with -# LDAP is done using clunky LDIFs, web interfaces, or with painful APIs that -# required a thick reference manual nearby. ActiveLdap aims to fix that. -# Inspired by ActiveRecord[http://activerecord.rubyonrails.org], ActiveLdap provides an -# object oriented interface to LDAP entries. -# -# The target audience is system administrators and LDAP users everywhere that -# need quick, clean access to LDAP in Ruby. -# -# === What's LDAP? -# -# LDAP stands for "Lightweight Directory Access Protocol." Basically this means -# that it is the protocol used for accessing LDAP servers. LDAP servers -# lightweight directories. An LDAP server can contain anything from a simple -# digital phonebook to user accounts for computer systems. More and more -# frequently, it is being used for the latter. My examples in this text will -# assume some familiarity with using LDAP as a centralized authentication and -# authorization server for Unix systems. (Unfortunately, I've yet to try this -# against Microsoft's ActiveDirectory, despite what the name implies.) -# -# Further reading: -# * RFC1777[http://www.faqs.org/rfcs/rfc1777.html] - Lightweight Directory Access Protocol -# * OpenLDAP[http://www.openldap.org] -# -# === So why use ActiveLdap? -# -# Well if you like to fumble around in the dark, dank innards of LDAP, you can -# quit reading now. However, if you'd like a cleaner way to integrate LDAP in to -# your existing code, hopefully that's why you'll want to use ActiveLdap. -# -# Using LDAP directly (even with the excellent Ruby/LDAP), leaves you bound to -# the world of the predefined LDAP API. While this API is important for many -# reasons, having to extract code out of LDAP search blocks and create huge -# arrays of LDAP.mod entries make code harder to read, less intuitive, and just -# less fun to write. Hopefully, ActiveLdap will remedy all of these -# problems! -# -# == Getting Started -# -# ActiveLdap does have some overhead when you get started. You must not -# only install the package and all of it's requirements, but you must also make -# customizations that will let it work in your environment. -# -# === Requirements -# -# * A Ruby implementation: Ruby[http://www.ruby-lang.org] 1.8.x, 1.9.1 or JRuby[http://jruby.codehaus.org/] -# * A LDAP library: Ruby/LDAP[http://code.google.com/p/ruby-activeldap/wiki/RubyLDAP] (for Ruby), Net::LDAP[http://rubyforge.org/projects/net-ldap/] (for Ruby or JRuby) or JNDI (for JRuby) -# * A LDAP server: OpenLDAP[http://www.openldap.org], etc -# - Your LDAP server must allow root_dse queries to allow for schema queries -# -# === Installation -# -# Assuming all the requirements are installed, you can install by gem. -# -# # gem install activeldap -# -# Now as a quick test, you can run: -# -# $ irb -rubygems -# irb> require 'active_ldap' -# => true -# irb> exit -# -# If the require returns false or an exception is raised, there has been a -# problem with the installation. You may need to customize what setup.rb does on -# install. -# -# == Usage -# -# This section covers using ActiveLdap from writing extension classes to -# writing applications that use them. -# -# Just to give a taste of what's to come, here is a quick example using irb: -# -# irb> require 'active_ldap' -# -# Call setup_connection method for connect to LDAP server. In this case, LDAP server -# is localhost, and base of LDAP tree is "dc=dataspill,dc=org". -# -# irb> ActiveLdap::Base.setup_connection :host => 'localhost', :base => 'dc=dataspill,dc=org' -# -# Here's an extension class that maps to the LDAP Group objects: -# -# irb> class Group < ActiveLdap::Base -# irb* ldap_mapping -# irb* end -# -# Here is the Group class in use: -# -# irb> all_groups = Group.find(:all, '*').collect {|group| group.cn} -# => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"] -# -# irb> group = Group.find("develop") -# => #<Group objectClass:<...> ...> -# -# irb> group.cn -# => "develop" -# -# irb> group.gid_number -# => "1003" -# -# That's it! No let's get back in to it. -# -# === Extension Classes -# -# Extension classes are classes that are subclassed from ActiveLdap::Base. They -# are used to represent objects in your LDAP server abstractly. -# -# ==== Why do I need them? -# -# Extension classes are what make ActiveLdap "active"! They do all the -# background work to make easy-to-use objects by mapping the LDAP object's -# attributes on to a Ruby class. -# -# -# ==== Special Methods -# -# I will briefly talk about each of the methods you can use when defining an -# extension class. In the above example, I only made one special method call -# inside the Group class. More than likely, you will want to more than that. -# -# ===== ldap_mapping -# -# ldap_mapping is the only required method to setup an extension class for use -# with ActiveLdap. It must be called inside of a subclass as shown above. -# -# Below is a much more realistic Group class: -# -# class Group < ActiveLdap::Base -# ldap_mapping :dn_attribute => 'cn', -# :prefix => 'ou=Groups', :classes => ['top', 'posixGroup'], -# :scope => :one -# end -# -# As you can see, this method is used for defining how this class maps in to LDAP. Let's say that -# my LDAP tree looks something like this: -# -# * dc=dataspill,dc=org -# |- ou=People,dc=dataspill,dc=org -# |+ ou=Groups,dc=dataspill,dc=org -# \ -# |- cn=develop,ou=Groups,dc=dataspill,dc=org -# |- cn=root,ou=Groups,dc=dataspill,dc=org -# |- ... -# -# Under ou=People I store user objects, and under ou=Groups, I store group -# objects. What |ldap_mapping| has done is mapped the class in to the LDAP tree -# abstractly. With the given :dn_attributes and :prefix, it will only work for -# entries under ou=Groups,dc=dataspill,dc=org using the primary attribute 'cn' -# as the beginning of the distinguished name. -# -# Just for clarity, here's how the arguments map out: -# -# cn=develop,ou=Groups,dc=dataspill,dc=org -# ^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ -# :dn_attribute | | -# :prefix | -# :base from setup_connection -# -# :scope tells ActiveLdap to only search under ou=Groups, and not to look deeper -# for dn_attribute matches. -# (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org) -# You can choose value from between :sub, :one and :base. -# -# Something's missing: :classes. :classes is used to tell ActiveLdap what -# the minimum requirement is when creating a new object. LDAP uses objectClasses -# to define what attributes a LDAP object may have. ActiveLdap needs to know -# what classes are required when creating a new object. Of course, you can leave -# that field out to default to ['top'] only. Then you can let each application -# choose what objectClasses their objects should have by calling the method e.g. -# Group#add_class(*values). -# -# Note that is can be very important to define the default :classes value. Due to -# implementation choices with most LDAP servers, once an object is created, its -# structural objectclasses may not be removed (or replaced). Setting a sane default -# may help avoid programmer error later. -# -# :classes isn't the only optional argument. If :dn_attribute is left off, -# it defaults to super class's value or 'cn'. If :prefix is left off, -# it will default to 'ou=PluralizedClassName'. In this -# case, it would be 'ou=Groups'. -# -# :classes should be an Array. :dn_attribute should be a String and so should -# :prefix. -# -# -# ===== belongs_to -# -# This method allows an extension class to make use of other extension classes -# tying objects together across the LDAP tree. Often, user objects will be -# members of, or belong_to, Group objects. -# -# * dc=dataspill,dc=org -# |+ ou=People,dc=dataspill,dc=org -# \ -# |- uid=drewry,ou=People,dc=dataspill,dc=org -# |- ou=Groups,dc=dataspill,dc=org -# -# -# In the above tree, one such example would be user 'drewry' who is a part of the -# group 'develop'. You can see this by looking at the 'memberUid' field of 'develop'. -# -# irb> develop = Group.find('develop') -# => ... -# irb> develop.memberUid -# => ['drewry', 'builder'] -# -# If we look at the LDAP entry for 'drewry', we do not see any references to -# group 'develop'. In order to remedy that, we can use belongs_to -# -# irb> class User < ActiveLdap::Base -# irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top','account'] -# irb* belongs_to :groups, :class_name => 'Group', :many => 'memberUid', :foreign_key => 'uid' -# irb* end -# -# Now, class User will have a method called 'groups' which will retrieve all -# Group objects that a user is in. -# -# irb> me = User.find('drewry') -# irb> me.groups -# => #<ActiveLdap::Association::BelongsToMany...> # Enumerable object -# irb> me.groups.each { |group| p group.cn };nil -# "cdrom" -# "audio" -# "develop" -# => nil -# (Note: nil is just there to make the output cleaner...) -# -# TIP: If you weren't sure what the distinguished name attribute was for Group, -# you could also do the following: -# -# irb> me.groups.each { |group| p group.id };nil -# "cdrom" -# "audio" -# "develop" -# => nil -# -# Now let's talk about the arguments. The first argument is the name of the -# method you wish to create. In this case, we created a method called groups -# using the symbol :groups. The next collection of arguments are actually a Hash -# (as with ldap_mapping). :class_name should be a string that has the name of a -# class you've already included. If you class is inside of a module, be sure to -# put the whole name, e.g. :class_name => "MyLdapModule::Group". :foreign_key -# tells belongs_to what attribute Group objects have that match the -# :many. :many is the name of the local attribute whose value -# should be looked up in Group under the foreign key. If :foreign_key is left -# off of the argument list, it is assumed to be the dn_attribute. With this in -# mind, the above definition could become: -# -# irb> class User < ActiveLdap::Base -# irb* ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top','account'] -# irb* belongs_to :groups, :class_name => 'Group', :many => 'memberUid' -# irb* end -# -# In addition, you can do simple membership tests by doing the following: -# -# irb> me.groups.member? 'root' -# => false -# irb> me.groups.member? 'develop' -# => true -# -# ===== has_many -# -# This method is the opposite of belongs_to. Instead of checking other objects in -# other parts of the LDAP tree to see if you belong to them, you have multiple -# objects from other trees listed in your object. To show this, we can just -# invert the example from above: -# -# class Group < ActiveLdap::Base -# ldap_mapping :dn_attribute => 'cn', :prefix => 'ou=Groups', :classes => ['top', 'posixGroup'] -# has_many :members, :class_name => "User", :wrap => "memberUid", :primary_key => 'uid' -# end -# -# Now we can see that group develop has user 'drewry' as a member, and it can -# even return all responses in object form just like belongs_to methods. -# -# irb> develop = Group.find('develop') -# => ... -# irb> develop.members -# => #<ActiveLdap::Association::HasManyWrap:..> # Enumerable object -# irb> develop.members.map{|member| member.id} -# => ["drewry", "builder"] -# -# The arguments for has_many follow the exact same idea that belongs_to's -# arguments followed. :wrap's contents are used to search for matching -# :primary_key content. If :primary_key is not specified, it defaults to the -# dn_attribute of the specified :class_name. -# -# === Using these new classes -# -# These new classes have many method calls. Many of them are automatically -# generated to provide access to the LDAP object's attributes. Other were defined -# during class creation by special methods like belongs_to. There are a few other -# methods that do not fall in to these categories. -# -# -# ==== .find -# -# .find is a class method that is accessible from -# any subclass of Base that has 'ldap_mapping' called. When -# called .first(:first) returns the first match of the given class. -# -# irb> Group.find(:first, 'deve*").cn -# => "develop" -# -# In this simple example, Group.find took the search string of 'deve*' and -# searched for the first match in Group where the dn_attribute matched the -# query. This is the simplest example of .find. -# -# irb> Group.find(:all).collect {|group| group.cn} -# => ["root", "daemon", "bin", "sys", "adm", "tty", ..., "develop"] -# -# Here .find(:all) returns all matches to the same query. Both .find(:first) and -# .find(:all) also can take more expressive arguments: -# -# irb> Group.find(:all, :attribute => 'gidNumber', :value => '1003').collect {|group| group.cn} -# => ["develop"] -# -# So it is pretty clear what :attribute and :value do - they are used to query as -# :attribute=:value. -# -# If :attribute is unspecified, it defaults to the dn_attribute. -# -# It is also possible to override :attribute and :value by specifying :filter. This -# argument allows the direct specification of a LDAP filter to retrieve objects by. -# -# ==== .search -# .search is a class method that is accessible from any subclass of Base, and Base. -# It lets the user perform an arbitrary search against the current LDAP connection -# irrespetive of LDAP mapping data. This is meant to be useful as a utility method -# to cover 80% of the cases where a user would want to use Base.connection directly. -# -# irb> Base.search(:base => 'dc=example,dc=com', :filter => '(uid=roo*)', -# :scope => :sub, :attributes => ['uid', 'cn']) -# => [["uid=root,ou=People,dc=dataspill,dc=org",{"cn"=>["root"], "uidNumber"=>["0"]}] -# You can specify the :filter, :base, :scope, and :attributes, but they all have defaults -- -# * :filter defaults to objectClass=* - usually this isn't what you want -# * :base defaults to the base of the class this is executed from (as set in ldap_mapping) -# * :scope defaults to :sub. Usually you won't need to change it (You can choose value also from between :one and :base) -# * :attributes defaults to [] and is the list of attributes you want back. Empty means all of them. -# -# ==== #valid? -# -# valid? is a method that verifies that all attributes that are required by the -# objects current objectClasses are populated. -# -# ==== #save -# -# save is a method that writes any changes to an object back to the LDAP server. -# It automatically handles the addition of new objects, and the modification of -# existing ones. -# -# ==== .exists? -# -# exists? is a simple method which returns true is the current object exists in -# LDAP, or false if it does not. -# -# irb> User.exists?("dshadsadsa") -# => false -# -# -# === ActiveLdap::Base -# -# ActiveLdap::Base has come up a number of times in the examples above. Every -# time, it was being used as the super class for the wrapper objects. While this -# is it's main purpose, it also handles quite a bit more in the background. -# -# ==== What is it? -# -# ActiveLdap::Base is the heart of ActiveLdap. It does all the schema -# parsing for validation and attribute-to-method mangling as well as manage the -# connection to LDAP. -# -# ===== setup_connection -# -# Base.setup_connection takes many (optional) arguments and is used to -# connect to the LDAP server. Sometimes you will want to connect anonymously -# and other times over TLS with user credentials. Base.setup_connection is -# here to do all of that for you. -# -# -# By default, if you call any subclass of Base, such as Group, it will call -# Base.setup_connection() if these is no active LDAP connection. If your -# server allows anonymous binding, and you only want to access data in a -# read-only fashion, you won't need to call Base.setup_connection. Here -# is a fully parameterized call: -# -# Base.setup_connection( -# :host => 'ldap.dataspill.org', -# :port => 389, -# :base => 'dc=dataspill,dc=org', -# :logger => logger_object, -# :bind_dn => "uid=drewry,ou=People,dc=dataspill,dc=org", -# :password_block => Proc.new { 'password12345' }, -# :allow_anonymous => false, -# :try_sasl => false -# ) -# -# There are quite a few arguments, but luckily many of them have safe defaults: -# * :host defaults to "127.0.0.1". -# * :port defaults to nil. 389 is applied if not specified. -# * :bind_dn defaults to nil. anonymous binding is applied if not specified. -# * :logger defaults to a Logger object that prints fatal messages to stderr -# * :password_block defaults to nil -# * :allow_anonymous defaults to true -# * :try_sasl defaults to false - see Advanced Topics for more on this one. -# -# -# Most of these are obvious, but I'll step through them for completeness: -# * :host defines the LDAP server hostname to connect to. -# * :port defines the LDAP server port to connect to. -# * :method defines the type of connection - :tls, :ssl, :plain -# * :base specifies the LDAP search base to use with the prefixes defined in all -# subclasses. -# * :bind_dn specifies what your server expects when attempting to bind with -# credentials. -# * :logger accepts a custom logger object to integrate with any other logging -# your application uses. -# * :password_block, if defined, give the Proc block for acquiring the password -# * :password, if defined, give the user's password as a String -# * :store_password indicates whether the password should be stored, or if used -# whether the :password_block should be called on each reconnect. -# * :allow_anonymous determines whether anonymous binding is allowed if other -# bind methods fail -# * :try_sasl, when true, tells ActiveLdap to attempt a SASL-GSSAPI bind -# * :sasl_quiet, when true, tells the SASL libraries to not spew messages to STDOUT -# * :sasl_options, if defined, should be a hash of options to pass through. This currently only works with the ruby-ldap adapter, which currently only supports :realm, :authcid, and :authzid. -# * :retry_limit - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite. -# * :retry_wait - seconds to wait before retrying a connection -# * :scope - dictates how to find objects. (Default: :one) -# * :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned. -# * :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true -# See lib/configuration.rb(ActiveLdap::Configuration::DEFAULT_CONFIG) for defaults for each option -# -# Base.setup_connection just setups connection -# configuration. A connection is connected and bound when it -# is needed. It follows roughly the following approach: -# -# * Connect to host:port using :method -# -# * If bind_dn and password_block/password, attempt to bind with credentials. -# * If that fails or no password_block and anonymous allowed, attempt to bind -# anonymously. -# * If that fails, error out. -# -# On connect, the configuration options passed in are stored -# in an internal class variable which is used to cache the -# information without ditching the defaults passed in from -# configuration.rb -# -# ===== connection -# -# Base.connection returns the ActiveLdap::Connection object. -# -# === Exceptions -# -# There are a few custom exceptions used in ActiveLdap. They are detailed below. -# -# ==== DeleteError -# -# This exception is raised when #delete fails. It will include LDAP error -# information that was passed up during the error. -# -# ==== SaveError -# -# This exception is raised when there is a problem in #save updating or creating -# an LDAP entry. Often the error messages are cryptic. Looking at the server -# logs or doing an Ethereal[http://www.ethereal.com] dump of the connection will -# often provide better insight. -# -# ==== AuthenticationError -# -# This exception is raised during Base.setup_connection if no valid authentication methods -# succeeded. -# -# ==== ConnectionError -# -# This exception is raised during Base.setup_connection if no valid -# connection to the LDAP server could be created. Check you -# Base.setup_connection arguments, and network connectivity! Also check -# your LDAP server logs to see if it ever saw the request. -# -# ==== ObjectClassError -# -# This exception is raised when an object class is used that is not defined -# in the schema. -# -# === Others -# -# Other exceptions may be raised by the Ruby/LDAP module, or by other subsystems. -# If you get one of these exceptions and think it should be wrapped, write me an -# email and let me know where it is and what you expected. For faster results, -# email a patch! -# -# === Putting it all together -# -# Now that all of the components of ActiveLdap have been covered, it's time -# to put it all together! The rest of this section will show the steps to setup -# example user and group management scripts for use with the LDAP tree described -# above. -# -# All of the scripts here are in the package's examples/ directory. -# -# ==== Setting up -# -# Create directory for scripts. -# -# mkdir -p ldapadmin/objects -# -# In ldapadmin/objects/ create the file user.rb: -# -# require 'objects/group' -# -# class User < ActiveLdap::Base -# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['person', 'posixAccount'] -# belongs_to :groups, :class_name => 'Group', :many => 'memberUid' -# end -# -# In ldapadmin/objects/ create the file group.rb: -# -# class Group < ActiveLdap::Base -# ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups' -# has_many :members, :class_name => "User", :wrap => "memberUid" -# has_many :primary_members, :class_name => 'User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber' -# end -# -# Now, we can write some small scripts to do simple management tasks. -# -# ==== Creating LDAP entries -# -# Now let's create a really dumb script for adding users - ldapadmin/useradd: -# -# #!/usr/bin/ruby -W0 -# -# base = File.expand_path(File.join(File.dirname(__FILE__), "..")) -# $LOAD_PATH << File.join(base, "lib") -# $LOAD_PATH << File.join(base, "examples") -# -# require 'rubygems' -# require 'active_ldap' -# require 'objects/user' -# require 'objects/group' -# -# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| -# opts.banner += " USER_NAME CN UID" -# end -# -# if argv.size == 3 -# name, cn, uid = argv -# else -# $stderr.puts opts -# exit 1 -# end -# -# pwb = Proc.new do |user| -# ActiveLdap::Command.read_password("[#{user}] Password: ") -# end -# -# ActiveLdap::Base.setup_connection(:password_block => pwb, -# :allow_anonymous => false) -# -# if User.exists?(name) -# $stderr.puts("User #{name} already exists.") -# exit 1 -# end -# -# user = User.new(name) -# user.add_class('shadowAccount') -# user.cn = cn -# user.uid_number = uid -# user.gid_number = uid -# user.home_directory = "/home/#{name}" -# user.sn = "somesn" -# unless user.save -# puts "failed" -# puts user.errors.full_messages -# exit 1 -# end -# -# ==== Managing LDAP entries -# -# Now let's create another dumb script for modifying users - ldapadmin/usermod: -# -# #!/usr/bin/ruby -W0 -# -# base = File.expand_path(File.join(File.dirname(__FILE__), "..")) -# $LOAD_PATH << File.join(base, "lib") -# $LOAD_PATH << File.join(base, "examples") -# -# require 'rubygems' -# require 'active_ldap' -# require 'objects/user' -# require 'objects/group' -# -# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| -# opts.banner += " USER_NAME CN UID" -# end -# -# if argv.size == 3 -# name, cn, uid = argv -# else -# $stderr.puts opts -# exit 1 -# end -# -# pwb = Proc.new do |user| -# ActiveLdap::Command.read_password("[#{user}] Password: ") -# end -# -# ActiveLdap::Base.setup_connection(:password_block => pwb, -# :allow_anonymous => false) -# -# unless User.exists?(name) -# $stderr.puts("User #{name} doesn't exist.") -# exit 1 -# end -# -# user = User.find(name) -# user.cn = cn -# user.uid_number = uid -# user.gid_number = uid -# unless user.save -# puts "failed" -# puts user.errors.full_messages -# exit 1 -# end -# -# ==== Removing LDAP entries -# Now let's create more one for deleting users - ldapadmin/userdel: -# -# #!/usr/bin/ruby -W0 -# -# base = File.expand_path(File.join(File.dirname(__FILE__), "..")) -# $LOAD_PATH << File.join(base, "lib") -# $LOAD_PATH << File.join(base, "examples") -# -# require 'rubygems' -# require 'active_ldap' -# require 'objects/user' -# require 'objects/group' -# -# argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| -# opts.banner += " USER_NAME" -# end -# -# if argv.size == 1 -# name = argv.shift -# else -# $stderr.puts opts -# exit 1 -# end -# -# pwb = Proc.new do |user| -# ActiveLdap::Command.read_password("[#{user}] Password: ") -# end -# -# ActiveLdap::Base.setup_connection(:password_block => pwb, -# :allow_anonymous => false) -# -# unless User.exists?(name) -# $stderr.puts("User #{name} doesn't exist.") -# exit 1 -# end -# -# User.destroy(name) -# -# === Advanced Topics -# -# Below are some situation tips and tricks to get the most out of ActiveLdap. -# -# -# ==== Binary data and other subtypes -# -# Sometimes, you may want to store attributes with language specifiers, or -# perhaps in binary form. This is (finally!) fully supported. To do so, -# follow the examples below: -# -# irb> user = User.new('drewry') -# => ... -# # This adds a cn entry in lang-en and whatever the server default is. -# irb> user.cn = [ 'wad', {'lang-en' => ['wad', 'Will Drewry']} ] -# => ... -# irb> user.cn -# => ["wad", {"lang-en-us" => ["wad", "Will Drewry"]}] -# # Now let's add a binary X.509 certificate (assume objectClass is correct) -# irb> user.user_certificate = File.read('example.der') -# => ... -# irb> user.save -# -# So that's a lot to take in. Here's what is going on. I just set the LDAP -# object's cn to "wad" and cn:lang-en-us to ["wad", "Will Drewry"]. -# Anytime a LDAP subtype is required, you must encapsulate the data in a Hash. -# -# But wait a minute, I just read in a binary certificate without wrapping it up. -# So any binary attribute _that requires ;binary subtyping_ will automagically -# get wrapped in {'binary' => value} if you don't do it. This keeps your #writes -# from breaking, and my code from crying. For correctness, I could have easily -# done the following: -# -# irb> user.user_certificate = {'binary' => File.read('example.der')} -# -# You should note that some binary data does not use the binary subtype all the time. -# One example is jpegPhoto. You can use it as jpegPhoto;binary or just as jpegPhoto. -# Since the schema dictates that it is a binary value, ActiveLdap will write -# it as binary, but the subtype will not be automatically appended as above. The -# use of the subtype on attributes like jpegPhoto is ultimately decided by the -# LDAP site policy and not by any programmatic means. -# -# The only subtypes defined in LDAPv3 are lang-* and binary. These can be nested -# though: -# -# irb> user.cn = [{'lang-ja' => {'binary' => 'some Japanese'}}] -# -# As I understand it, OpenLDAP does not support nested subtypes, but some -# documentation I've read suggests that Netscape's LDAP server does. I only -# have access to OpenLDAP. If anyone tests this out, please let me know how it -# goes! -# -# -# And that pretty much wraps up this section. -# -# ==== Further integration with your environment aka namespacing -# -# If you want this to cleanly integrate into your system-wide Ruby include path, -# you should put your extension classes inside a custom module. -# -# -# Example: -# -# ./myldap.rb: -# require 'active_ldap' -# require 'myldap/user' -# require 'myldap/group' -# module MyLDAP -# end -# -# ./myldap/user.rb: -# module MyLDAP -# class User < ActiveLdap::Base -# ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People', :classes => ['top', 'account', 'posixAccount'] -# belongs_to :groups, :class_name => 'MyLDAP::Group', :many => 'memberUid' -# end -# end -# -# ./myldap/group.rb: -# module MyLDAP -# class Group < ActiveLdap::Base -# ldap_mapping :classes => ['top', 'posixGroup'], :prefix => 'ou=Groups' -# has_many :members, :class_name => 'MyLDAP::User', :wrap => 'memberUid' -# has_many :primary_members, :class_name => 'MyLDAP::User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber' -# end -# end -# -# Now in your local applications, you can call -# -# require 'myldap' -# -# MyLDAP::Group.new('foo') -# ... -# -# and everything should work well. -# -# -# ==== force array results for single values -# -# Even though ActiveLdap attempts to maintain programmatic ease by -# returning Array values only. By specifying 'true' as an argument to -# any attribute method you will get back a Array if it is single value. -# Here's an example: -# -# irb> user = User.new('drewry') -# => ... -# irb> user.cn(true) -# => ["Will Drewry"] -# -# ==== Dynamic attribute crawling -# -# If you use tab completion in irb, you'll notice that you /can/ tab complete the dynamic -# attribute methods. You can still see which methods are for attributes using -# Base#attribute_names: -# -# irb> d = Group.new('develop') -# => ... -# irb> d.attribute_names -# => ["gidNumber", "cn", "memberUid", "commonName", "description", "userPassword", "objectClass"] -# -# -# ==== Juggling multiple LDAP connections -# -# In the same vein as the last tip, you can use multiple LDAP connections by -# per class as follows: -# -# irb> anon_class = Class.new(Base) -# => ... -# irb> anon_class.setup_connection -# => ... -# irb> auth_class = Class.new(Base) -# => ... -# irb> auth_class.setup_connection(:password_block => lambda{'mypass'}) -# => ... -# -# This can be useful for doing authentication tests and other such tricks. -# -# ==== :try_sasl -# -# If you have the Ruby/LDAP package with the SASL/GSSAPI patch from Ian -# MacDonald's web site, you can use Kerberos to bind to your LDAP server. By -# default, :try_sasl is false. -# -# Also note that you must be using OpenLDAP 2.1.29 or higher to use SASL/GSSAPI -# due to some bugs in older versions of OpenLDAP. -# -# ==== Don't be afraid! [Internals] -# -# Don't be afraid to add more methods to the extensions classes and to -# experiment. That's exactly how I ended up with this package. If you come up -# with something cool, please share it! -# -# The internal structure of ActiveLdap::Base, and thus all its subclasses, is -# still in flux. I've tried to minimize the changes to the overall API, but -# the internals are still rough around the edges. -# -# ===== Where's ldap_mapping data stored? How can I get to it? -# -# When you call ldap_mapping, it overwrites several class methods inherited -# from Base: -# * Base.base() -# * Base.required_classes() -# * Base.dn_attribute() -# You can access these from custom class methods by calling MyClass.base(), -# or whatever. There are predefined instance methods for getting to these -# from any new instance methods you define: -# * Base#base() -# * Base#required_classes() -# * Base#dn_attribute() -# -# ===== What else? -# -# Well if you want to use the LDAP connection for anything, I'd suggest still -# calling Base.connection to get it. There really aren't many other internals -# that need to be worried about. You could get the LDAP schema with -# Base.schema. -# -# The only other useful tricks are dereferencing and accessing the stored -# data. Since LDAP attributes can have multiple names, e.g. cn or commonName, -# any methods you write might need to figure it out. I'd suggest just -# calling self[attribname] to get the value, but if that's not good enough, -# you can call look up the stored name by #to_real_attribute_name as follows: -# -# irb> User.find(:first).instance_eval do -# irb> to_real_attribute_name('commonName') -# irb> end -# => 'cn' -# -# This tells you the name the attribute is stored in behind the scenes (@data). -# Again, self[attribname] should be enough for most extensions, but if not, -# it's probably safe to dabble here. -# -# Also, if you like to look up all aliases for an attribute, you can call the -# following: -# -# irb> User.schema.attribute_type 'cn', 'NAME' -# => ["cn", "commonName"] -# -# This is discovered automagically from the LDAP server's schema. -# -# == Limitations -# -# === Speed -# -# Currently, ActiveLdap could be faster. I have some recursive type -# checking going on which slows object creation down, and I'm sure there -# are many, many other places optimizations can be done. Feel free -# to send patches, or just hang in there until I can optimize away the -# slowness. -# -# == Feedback -# -# Any and all feedback and patches are welcome. I am very excited about this -# package, and I'd like to see it prove helpful to more people than just myself. -# +require "rubygems" +require "active_model" +require "active_support/core_ext" -require_gem_if_need = Proc.new do |library_name, gem_name, *gem_args| - gem_name ||= library_name - begin - if !gem_args.empty? and Object.const_defined?(:Gem) - gem gem_name, *gem_args - end - require library_name - rescue LoadError - require 'rubygems' - gem gem_name, *gem_args - require library_name - end -end - -require_gem_if_need.call("active_support", "activesupport", "~> 2.3.11") - -# TODO: This should be removed when Rails 3 is supported. -ActiveSupport::Dependencies.autoload_paths << File.expand_path(File.dirname(__FILE__)) - module ActiveLdap - VERSION = "1.2.4" + VERSION = "3.1.0" + autoload :Command, "active_ldap/command" end if RUBY_PLATFORM.match('linux') require 'active_ldap/timeout' else require 'active_ldap/timeout_stub' end -require_gem_if_need.call("active_record", "activerecord", "~> 2.3.11") begin - require_gem_if_need.call("locale", nil, "= 2.0.5") - require_gem_if_need.call("fast_gettext", nil, "= 0.5.8") + require "locale" + require "fast_gettext" rescue LoadError end require 'active_ldap/get_text' require 'active_ldap/compatible' @@ -930,10 +26,12 @@ require 'active_ldap/distinguished_name' require 'active_ldap/ldif' require 'active_ldap/xml' +require 'active_ldap/persistence' + require 'active_ldap/associations' require 'active_ldap/attributes' require 'active_ldap/configuration' require 'active_ldap/connection' require 'active_ldap/operations' @@ -956,20 +54,23 @@ include ActiveLdap::Attributes include ActiveLdap::Configuration include ActiveLdap::Connection include ActiveLdap::Operations include ActiveLdap::ObjectClass - include ActiveLdap::HumanReadable + include ActiveLdap::Persistence + include ActiveLdap::Acts::Tree include ActiveLdap::Validations include ActiveLdap::Callbacks + include ActiveLdap::HumanReadable end unless defined?(ACTIVE_LDAP_CONNECTION_ADAPTERS) ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap net_ldap jndi) end ACTIVE_LDAP_CONNECTION_ADAPTERS.each do |adapter| require "active_ldap/adapter/#{adapter}" end +