From codesite-noreply at google.com Sat May 5 21:24:12 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 05 May 2007 18:24:12 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r161 - trunk/lib/active_ldap Message-ID: <163600d1b5042fc30abdbbe4ba8@google.com> Author: koutou Date: Sat May 5 18:23:57 2007 New Revision: 161 Modified: trunk/lib/active_ldap/base.rb Log: * ActiveLdap::Base#methods: don't return objectClass related names. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Sat May 5 18:23:57 2007 @@ -944,7 +944,8 @@ # Add available attributes to the methods def methods(inherited_too=true) ensure_apply_object_class - target_names = @attr_methods.keys + @attr_aliases.keys - ['objectClass'] + target_names = @attr_methods.keys + @attr_aliases.keys + target_names -= ['objectClass', Inflector.underscore('objectClass')] super + target_names.uniq.collect do |x| [x, "#{x}=", "#{x}?", "#{x}_before_type_cast"] end.flatten From codesite-noreply at google.com Sat May 5 21:25:08 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 05 May 2007 18:25:08 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r162 - trunk/test Message-ID: <163600d103042fc30e156c7948f4b@google.com> Author: koutou Date: Sat May 5 18:24:52 2007 New Revision: 162 Modified: trunk/test/test_find.rb Log: * added tests for find with sort. Modified: trunk/test/test_find.rb ============================================================================== --- trunk/test/test_find.rb (original) +++ trunk/test/test_find.rb Sat May 5 18:24:52 2007 @@ -4,6 +4,28 @@ include AlTestUtils priority :must + def test_find_with_sort + make_temporary_user(:uid => "user1") do |user1,| + make_temporary_user(:uid => "user2") do |user2,| + users = @user_class.find(:all, :sort_by => "uid", :order => 'asc') + assert_equal(["user1", "user2"], users.collect {|u| u.uid}) + users = @user_class.find(:all, :sort_by => "uid", :order => 'desc') + assert_equal(["user2", "user1"], users.collect {|u| u.uid}) + + users = @user_class.find(:all, :order => 'asc') + assert_equal(["user1", "user2"], users.collect {|u| u.uid}) + users = @user_class.find(:all, :order => 'desc') + assert_equal(["user2", "user1"], users.collect {|u| u.uid}) + + users = @user_class.find(:all, :order => 'asc', :limit => 1) + assert_equal(["user1"], users.collect {|u| u.uid}) + users = @user_class.find(:all, :order => 'desc', :limit => 1) + assert_equal(["user2"], users.collect {|u| u.uid}) + end + end + end + + priority :normal def test_split_search_value assert_split_search_value([nil, "test-user", nil], "test-user") assert_split_search_value([nil, "test-user", "ou=Sub"], "test-user,ou=Sub") From codesite-noreply at google.com Sun May 6 01:07:32 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 05 May 2007 22:07:32 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r163 - trunk/lib Message-ID: <163600d406042fc6296fc4004d76f@google.com> Author: koutou Date: Sat May 5 22:07:09 2007 New Revision: 163 Modified: trunk/lib/active_ldap.rb Log: * updated document. Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Sat May 5 22:07:09 2007 @@ -1,21 +1,21 @@ #!/usr/bin/ruby -# = Ruby/ActiveLdap +# = Ruby/ActiveLdap # # "Ruby/ActiveLdap" Copyright (C) 2004,2005 Will Drewry mailto:will at alum.bu.edu # # == Introduction -# +# # Ruby/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. Ruby/ActiveLdap aims to fix that. # Inspired by ActiveRecord[http://activerecord.rubyonrails.org], Ruby/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 @@ -24,32 +24,32 @@ # 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: +# +# Further reading: # * RFC1777[http://www.faqs.org/rfcs/rfc1777.html] - Lightweight Directory Access Protocol # * OpenLDAP[http://www.openldap.org] -# +# # === So why use Ruby/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 Ruby/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, Ruby/ActiveLdap will remedy all of these # problems! -# +# # == Getting Started -# +# # Ruby/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 -# +# # * Ruby[http://www.ruby-lang.org] 1.8.x # * Ruby/LDAP[http://ruby-ldap.sourceforge.net] # * Log4r[http://log4r.sourceforge.net] @@ -57,134 +57,134 @@ # * An LDAP server compatible with Ruby/LDAP: OpenLDAP[http://www.openldap.org], etc # - Your LDAP server must allow root_dse queries to allow for schema queries # * Examples also require: Ruby/Password[http://raa.ruby-lang.org/project/ruby-password/] -# +# # === Installation -# +# # Assuming all the requirements are installed, you can install by grabbing the latest tgz file from # the download site[http://projects.dataspill.org/libraries/ruby/activeldap/download.html]. -# +# # The following steps will get the Ruby/ActiveLdap installed in no time! -# +# # $ tar -xzvf ruby-activeldap-current.tgz # $ cd ruby-activeldap-VERSION -# +# # Edit lib/active_ldap/configuration.rb replacing values to match what will work # with your LDAP servers. Please note that those variables are required, but can # be overridden in any program as detailed later in this document. Also make # sure that "ROOT" stays all upcase. -# +# # Now run: -# +# # $ ruby setup.rb config # $ ruby setup.rb setup # $ (as root) ruby setup.rb install -# +# # Now as a quick test, you can run: -# +# # $ irb # 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. -# -# +# +# # === Customizations -# +# # Now that Ruby/ActiveLdap is installed and working, we still have a few more # steps to make it useful for programming. -# +# # Let's say that you are writing a Ruby program for managing user and group # accounts in LDAP. I will use this as the running example throughout the # document. -# +# # You will want to make a directory called 'ldapadmin' wherever is convenient. Under this directory, # you'll want to make sure you have a 'lib' directory. -# +# # $ cd ~ # $ mkdir ldapadmin # $ cd ldapadmin # $ mkdir lib # $ cd lib -# +# # The lib directory is where we'll be making customizations. You can, of course, # make this changes somewhere in Ruby's default search path to make this # accessible to every Ruby scripts. Enough of my babbling, I'm sure you'd like to # know what we're going to put in lib/. -# +# # We're going to put extension classes in there. What are extension classes you say . . . -# -# +# +# # == Usage -# +# # This section covers using Ruby/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' -# +# # 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") # => # -# +# # irb> group.members.collect {|member| member.uid} # => ["drewry"] -# +# # 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 Ruby/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 Ruby/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 => LDAP::LDAP_SCOPE_ONELEVEL # end -# -# As you can see, this method is used for defining how this class maps in to LDAP. Let's say that +# +# 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 @@ -192,24 +192,24 @@ # |- 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 :dnattr 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 configuration.rb -# +# # :scope tells ActiveLdap to only search under ou=Groups, and not to look deeper # for dnattr matches. (e.g. cn=develop,ou=DevGroups,ou=Groups,dc=dataspill,dc=org) -# +# # Something's missing: :classes. :classes is used to tell Ruby/ActiveLdap what # the minimum requirement is when creating a new object. LDAP uses objectClasses # to define what attributes a LDAP object may have. Ruby/ActiveLdap needs to know @@ -222,48 +222,48 @@ # 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 underscored class name or 'cn'. If :prefix is left off, # it will default to 'ou=PLURALIZED_CLASSNAME'. 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 => 'People', :classes => ['top','account'] # irb* belongs_to :groups, :class => '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 # => [#, #, ...] @@ -273,16 +273,16 @@ # "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 @@ -294,78 +294,78 @@ # should be looked up in Group under the primary 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 :dnattr => 'uid', :prefix => 'People', :classes => ['top','account'] # irb* belongs_to :groups, :class => '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 => "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 # => [#, #] -# -# +# +# # 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. -# +# # === 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 it returns the first match of the given # class. -# +# # irb> Group.find('*').cn # => "root" -# +# # In this simple example, Group.find took the search string of 'deve*' and # searched for the first match in Group where the dnattr 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 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 @@ -387,87 +387,83 @@ # * :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 Ruby/ActiveLdap. It does all the schema # parsing for validation and attribute-to-method mangling as well as manage the # connection to LDAP. -# +# # ===== establish_connection -# +# # Base.establish_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.connect is here to do -# all of that for you. -# -# +# and other times over TLS with user credentials. Base.establish_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.establish_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.establish_connection. Here # is a fully parameterized call: -# -# Base.connect( +# +# Base.establish_connection( # :host => 'ldap.dataspill.org', # :port => 389, # :base => 'dc=dataspill,dc=org', -# :bind_format => "uid=%s,ou=People,dc=dataspill,dc=org", # :logger => log4r_obj, -# :user => 'drewry', +# :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 @@host from configuration.rb waaay back at the setup.rb stage.@ # * :port defaults to @@port from configuration.rb as well # * :base defaults to Base.base() from configuration.rb -# * :bind_format defaults @@bind_format from configuration.rb +# * :bind_dn defaults @@bind_format from configuration.rb # * :logger defaults to a Log4r object that prints fatal messages to stderr -# * :user defaults to ENV['user'] # * :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_format specifies what your server expects when attempting to bind with +# * :bind_dn specifies what your server expects when attempting to bind with # credentials. # * :logger accepts a custom log4r object to integrate with any other logging # your application uses. -# * :user gives the username to substitute into bind_format for binding with -# credentials # * :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 @@ -483,13 +479,13 @@ # * :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 for defaults for each option -# +# # Base.establish_connection both connects and binds in one step. It follows # roughly the following approach: -# +# # * Connect to host:port using :method -# -# * If user and password_block/password, attempt to bind with credentials. +# +# * 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. @@ -497,62 +493,62 @@ # On connect, the configuration options passed in are stored in an internal class variable # @configuration 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 Ruby/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.establish_connection if no valid authentication methods # succeeded. -# +# # ==== ConnectionError -# -# This exception is raised during Base.connect if no valid connection to the -# LDAP server could be created. Check you configuration.rb, Base.connect -# arguments, and network connectivity! Also check your LDAP server logs to see -# if it ever saw the request. +# +# This exception is raised during Base.establish_connection if no valid +# connection to the LDAP server could be created. Check you configuration.rb, +# Base.establish_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 Ruby/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 lib/ -# +# # In ldapadmin/lib/ create the file user.rb: # cat < 'Group', :wrap => 'memberUid' # end # EOF -# +# # In ldapadmin/lib/ create the file group.rb: # cat < 'User', :foreign_key => 'gidNumber', :primary_key => 'gidNumber' # end # Group # EOF -# +# # 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 -# +# # require 'active_ldap' # require 'lib/user' # require 'lib/group' # require 'password' -# +# # argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| # opts.banner += " USER_NAME CN UID" # end @@ -620,15 +616,15 @@ # end # # ==== Managing LDAP entries -# +# # Now let's create another dumb script for modifying users - ldapadmin/usermod: -# +# # #!/usr/bin/ruby -W0 -# +# # require 'active_ldap' # require 'lib/user' # require 'lib/group' -# +# # argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| # opts.banner += " USER_NAME CN UID" # end @@ -663,16 +659,16 @@ # end # # ==== Removing LDAP entries -# +# # And finally, a dumb script for removing user - ldapadmin/userdel: -# -# +# +# # #!/usr/bin/ruby -W0 -# +# # require 'active_ldap' # require 'lib/user' # require 'lib/group' -# +# # argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| # opts.banner += " USER_NAME" # end @@ -699,9 +695,9 @@ # User.destroy(name) # # === Advanced Topics -# +# # Below are some situation tips and tricks to get the most out of Ruby/ActiveLdap. -# +# # # ==== Binary data and other subtypes # @@ -721,14 +717,14 @@ # => ... # irb> user.save # -# So that's a lot to take in. Here's what is going on. I just set the LDAP +# 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 +# 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')} @@ -737,37 +733,37 @@ # 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, Ruby/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 +# 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-JP-jp' => {'binary' => 'somejp'}}] -# +# # 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 +# 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 @@ -775,7 +771,7 @@ # belongs_to :groups, :class => 'MyLDAP::Group', :many => 'memberUid' # end # end -# +# # ./myldap/group.rb: # module MyLDAP # class Group < ActiveLdap::Base @@ -784,21 +780,21 @@ # has_many :primary_members, :class => '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 Ruby/ActiveLdap attempts to maintain programmatic ease by -# returning Array values only. By specifying 'true' as an argument to +# Even though Ruby/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: # @@ -808,33 +804,33 @@ # => ["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 +# +# 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.establish_connection # => ... # irb> auth_class = Class.new(Base) # => ... -# irb> auth_class.establish_connection(password_block => {'mypass'}) +# irb> auth_class.establish_connection(:password_block => {'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 @@ -898,7 +894,7 @@ # == Limitations # # === Speed -# +# # Currently, Ruby/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 From codesite-noreply at google.com Sun May 6 01:12:39 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 05 May 2007 22:12:39 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r164 - trunk/lib/active_ldap Message-ID: <163600d8e4042fc63bbd70214da49@google.com> Author: koutou Date: Sat May 5 22:12:22 2007 New Revision: 164 Modified: trunk/lib/active_ldap/base.rb Log: * defined ActiveLdap::Base#to_param for ActionController. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Sat May 5 22:12:22 2007 @@ -860,6 +860,10 @@ get_attribute(dn_attribute) end + def to_param + id + end + def dn=(value) set_attribute(dn_attribute, value) end From codesite-noreply at google.com Sun May 6 08:21:10 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sun, 06 May 2007 05:21:10 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r165 - trunk/test Message-ID: Author: koutou Date: Sun May 6 05:20:52 2007 New Revision: 165 Modified: trunk/test/test_object_class.rb Log: * used AUXILIARY objectClass 'labeledURIObject' instead of STRUCTURAL objectClass 'room'. * used non-destructive method Array#+ instead of destructive method Array#<<. Modified: trunk/test/test_object_class.rb ============================================================================== --- trunk/test/test_object_class.rb (original) +++ trunk/test/test_object_class.rb Sun May 6 05:20:52 2007 @@ -6,10 +6,10 @@ priority :must def test_ensure_recommended_classes make_temporary_group do |group| - added_class = "room" + added_class = "labeledURIObject" assert_equal([], group.class.recommended_classes) - group.class.recommended_classes << added_class + group.class.recommended_classes += [added_class] assert_equal([added_class], group.class.recommended_classes) assert_equal([added_class], From codesite-noreply at google.com Mon May 7 01:22:54 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sun, 06 May 2007 22:22:54 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r166 - in trunk/lib: . active_ldap active_ldap/adapter active_ldap/adaptor Message-ID: <163600d103042fda7e3e9533709d1@google.com> Author: koutou Date: Sun May 6 22:22:35 2007 New Revision: 166 Added: trunk/lib/active_ldap/adapter/ - copied from r165, /trunk/lib/active_ldap/adaptor/ Removed: trunk/lib/active_ldap/adaptor/ Modified: trunk/lib/active_ldap.rb trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/adapter/ldap.rb trunk/lib/active_ldap/configuration.rb trunk/lib/active_ldap/connection.rb Log: * adaptor -> adapter. Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Sun May 6 22:22:35 2007 @@ -939,7 +939,7 @@ require 'active_ldap/attributes' require 'active_ldap/object_class' require 'active_ldap/distinguished_name' -require 'active_ldap/adaptor/ldap' +require 'active_ldap/adapter/ldap' require_gem_if_need.call("active_record/base", "activerecord") require 'active_ldap/validations' Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- /trunk/lib/active_ldap/adaptor/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Sun May 6 22:22:35 2007 @@ -1,7 +1,7 @@ module ActiveLdap - module Adaptor + module Adapter class Base - VALID_ADAPTOR_CONFIGURATION_KEYS = [:host, :port, :method, :timeout, + VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :timeout, :retry_on_timeout, :retry_limit, :retry_wait, :bind_dn, :password, :password_block, :try_sasl, @@ -11,8 +11,8 @@ @connection = nil @configuration = configuration.dup @logger = @configuration.delete(:logger) - @configuration.assert_valid_keys(VALID_ADAPTOR_CONFIGURATION_KEYS) - VALID_ADAPTOR_CONFIGURATION_KEYS.each do |name| + @configuration.assert_valid_keys(VALID_ADAPTER_CONFIGURATION_KEYS) + VALID_ADAPTER_CONFIGURATION_KEYS.each do |name| instance_variable_set("@#{name}", configuration[name]) end end Modified: trunk/lib/active_ldap/adapter/ldap.rb ============================================================================== --- /trunk/lib/active_ldap/adaptor/ldap.rb (original) +++ trunk/lib/active_ldap/adapter/ldap.rb Sun May 6 22:22:35 2007 @@ -5,7 +5,7 @@ require 'active_ldap/ldap' require 'active_ldap/schema' -require 'active_ldap/adaptor/base' +require 'active_ldap/adapter/base' class LDAP::Mod unless instance_method(:to_s).arity.zero? @@ -24,7 +24,7 @@ end module ActiveLdap - module Adaptor + module Adapter class Ldap < Base module Method class SSL Modified: trunk/lib/active_ldap/configuration.rb ============================================================================== --- trunk/lib/active_ldap/configuration.rb (original) +++ trunk/lib/active_ldap/configuration.rb Sun May 6 22:22:35 2007 @@ -78,7 +78,7 @@ @@defined_configurations.delete_if {|key, value| value == config} end - CONNECTION_CONFIGURATION_KEYS = [:base, :ldap_scope, :adaptor] + CONNECTION_CONFIGURATION_KEYS = [:base, :ldap_scope, :adapter] def remove_connection_related_configuration(config) config.reject do |key, value| CONNECTION_CONFIGURATION_KEYS.include?(key) Modified: trunk/lib/active_ldap/connection.rb ============================================================================== --- trunk/lib/active_ldap/connection.rb (original) +++ trunk/lib/active_ldap/connection.rb Sun May 6 22:22:35 2007 @@ -45,19 +45,19 @@ conn end - def connection=(adaptor) - if adaptor.is_a?(Adaptor::Base) + def connection=(adapter) + if adapter.is_a?(Adapter::Base) @schema = nil - active_connections[active_connection_name] = adaptor - elsif adaptor.is_a?(Hash) - config = adaptor - adaptor = Inflector.camelize(config[:adaptor] || "ldap") + active_connections[active_connection_name] = adapter + elsif adapter.is_a?(Hash) + config = adapter + adapter = Inflector.camelize(config[:adapter] || "ldap") config = remove_connection_related_configuration(config) - self.connection = Adaptor.const_get(adaptor).new(config) - elsif adaptor.nil? + self.connection = Adapter.const_get(adapter).new(config) + elsif adapter.nil? raise ConnectionNotEstablished else - establish_connection(adaptor) + establish_connection(adapter) end end From codesite-noreply at google.com Mon May 7 02:19:02 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sun, 06 May 2007 23:19:02 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r167 - in trunk: lib lib/active_ldap lib/active_ldap/adapter test Message-ID: <163600d8e4042fdb47077dab7333f@google.com> Author: koutou Date: Sun May 6 23:18:41 2007 New Revision: 167 Added: trunk/lib/active_ldap/adapter/ldap_ext.rb trunk/lib/active_ldap/ldap_error.rb Removed: trunk/lib/active_ldap/ldap.rb Modified: trunk/lib/active_ldap.rb trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/adapter/ldap.rb trunk/lib/active_ldap/base.rb trunk/lib/active_ldap/connection.rb trunk/test/al-test-utils.rb Log: * moved Ruby/LDAP dependencies to adapter/ldap.rb and adapter/ldap_ext.rb. Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Sun May 6 23:18:41 2007 @@ -939,7 +939,6 @@ require 'active_ldap/attributes' require 'active_ldap/object_class' require 'active_ldap/distinguished_name' -require 'active_ldap/adapter/ldap' require_gem_if_need.call("active_record/base", "activerecord") require 'active_ldap/validations' @@ -957,6 +956,14 @@ include ActiveLdap::Associations include ActiveLdap::Validations include ActiveLdap::Callbacks +end + +unless defined?(ACTIVE_LDAP_CONNECTION_ADAPTERS) + ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap) +end + +ACTIVE_LDAP_CONNECTION_ADAPTERS.each do |adapter| + require "active_ldap/adapter/#{adapter}" end $VERBOSE = verbose Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Sun May 6 23:18:41 2007 @@ -1,3 +1,6 @@ +require 'active_ldap/schema' +require 'active_ldap/ldap_error' + module ActiveLdap module Adapter class Base Modified: trunk/lib/active_ldap/adapter/ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap.rb (original) +++ trunk/lib/active_ldap/adapter/ldap.rb Sun May 6 23:18:41 2007 @@ -1,30 +1,18 @@ -require 'ldap' -require 'ldap/ldif' -require 'ldap/schema' - -require 'active_ldap/ldap' -require 'active_ldap/schema' - require 'active_ldap/adapter/base' -class LDAP::Mod - unless instance_method(:to_s).arity.zero? - def to_s - inspect - end - end - - alias_method :_initialize, :initialize - def initialize(op, type, vals) - if (LDAP::VERSION.split(/\./).collect {|x| x.to_i} <=> [0, 9, 7]) <= 0 - @op, @type, @vals = op, type, vals # to protect from GC - end - _initialize(op, type, vals) - end -end - module ActiveLdap module Adapter + class Base + class << self + def ldap_connection(options) + unless defined?(::LDAP) + require 'active_ldap/adapter/ldap_ext' + end + Ldap.new(options) + end + end + end + class Ldap < Base module Method class SSL @@ -46,12 +34,6 @@ end end - SCOPE = { - :base => LDAP::LDAP_SCOPE_BASE, - :sub => LDAP::LDAP_SCOPE_SUBTREE, - :one => LDAP::LDAP_SCOPE_ONELEVEL, - } - def connect(options={}) method = ensure_method(options[:method] || @method) host = options[:host] || @host @@ -85,7 +67,7 @@ key = 'subschemaSubentry' base ||= @connection.root_dse([key], sec, usec)[0][key][0] base ||= 'cn=schema' - result = @connection.search2(base, LDAP::LDAP_SCOPE_BASE, + result = @connection.search2(base, ensure_scope(:base), '(objectClass=subschema)', attrs, false, sec, usec).first Schema.new(result) @@ -139,11 +121,8 @@ elsif allow_anonymous and bind_as_anonymous(options) @logger.info {'Bound anonymous'} else - if @connection.err.zero? - message = 'All authentication methods exhausted.' - else - message = LDAP.err2string(@connection.err) - end + message = @connection.error_message + message ||= 'All authentication methods exhausted.' raise AuthenticationError, message end @@ -196,7 +175,7 @@ break if limit and limit >= i end end - rescue LDAP::Error + rescue LdapError # Do nothing on failure @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " + "for #{filter} and attrs #{attrs.inspect}"} @@ -240,7 +219,7 @@ @connection.delete(target) end end - rescue LDAP::NoSuchObject + rescue LdapError::NoSuchObject raise EntryNotFound, "No such entry: #{target}" end end @@ -250,18 +229,18 @@ operation(options) do @connection.add(dn, parse_entries(entries)) end - rescue LDAP::NoSuchObject + rescue LdapError::NoSuchObject raise EntryNotFound, "No such entry: #{dn}" - rescue LDAP::InvalidDnSyntax + rescue LdapError::InvalidDnSyntax raise DistinguishedNameInvalid.new(dn) - rescue LDAP::AlreadyExists + rescue LdapError::AlreadyExists raise EntryAlreadyExist, "#{$!.message}: #{dn}" - rescue LDAP::StrongAuthRequired + rescue LdapError::StrongAuthRequired raise StrongAuthenticationRequired, "#{$!.message}: #{dn}" - rescue LDAP::ObjectClassViolation + rescue LdapError::ObjectClassViolation raise RequiredAttributeMissed, "#{$!.message}: #{dn}" - rescue LDAP::UnwillingToPerform - raise UnwillingToPerform, "#{$!.message}: #{dn}" + rescue LdapError::UnwillingToPerform + raise OperationNotPermitted, "#{$!.message}: #{dn}" end end @@ -270,9 +249,9 @@ operation(options) do @connection.modify(dn, parse_entries(entries)) end - rescue LDAP::UndefinedType + rescue LdapError::UndefinedType raise - rescue LDAP::ObjectClassViolation + rescue LdapError::ObjectClassViolation raise RequiredAttributeMissed, "#{$!.message}: #{dn}" end end @@ -286,8 +265,8 @@ begin block.call rescue LDAP::ResultError - raise *LDAP::err2exception(@connection.err) if @connection.err != 0 - raise + @connection.assert_error_code + raise $!.message end end end @@ -318,9 +297,14 @@ end def ensure_scope(scope) - value = SCOPE[scope || :sub] + scope_map = { + :base => LDAP::LDAP_SCOPE_BASE, + :sub => LDAP::LDAP_SCOPE_SUBTREE, + :one => LDAP::LDAP_SCOPE_ONELEVEL, + } + value = scope_map[scope || :sub] if value.nil? - available_scopes = SCOPE.keys.collect {|s| s.inspect} + available_scopes = scope_map.keys.collect {|s| s.inspect} raise ArgumentError, "#{scope.inspect} is not one of the available " + "LDAP scope #{available_scopes}" end @@ -371,10 +355,10 @@ @connection.bind(bind_dn, passwd) true end - rescue LDAP::InvalidDnSyntax + rescue LdapError::InvalidDnSyntax @logger.debug {"DN is invalid: #{bind_dn}"} raise DistinguishedNameInvalid.new(bind_dn) - rescue LDAP::InvalidCredentials + rescue LdapError::InvalidCredentials false end end Added: trunk/lib/active_ldap/adapter/ldap_ext.rb ============================================================================== --- (empty file) +++ trunk/lib/active_ldap/adapter/ldap_ext.rb Sun May 6 23:18:41 2007 @@ -0,0 +1,68 @@ +require 'ldap' +require 'ldap/ldif' +require 'ldap/schema' + +module LDAP + class Mod + unless instance_method(:to_s).arity.zero? + def to_s + inspect + end + end + + alias_method :_initialize, :initialize + def initialize(op, type, vals) + if (LDAP::VERSION.split(/\./).collect {|x| x.to_i} <=> [0, 9, 7]) <= 0 + @op, @type, @vals = op, type, vals # to protect from GC + end + _initialize(op, type, vals) + end + end + + IMPLEMENT_SPECIFIC_ERRORS = {} + { + 0x51 => "SERVER_DOWN", + 0x52 => "LOCAL_ERROR", + 0x53 => "ENCODING_ERROR", + 0x54 => "DECODING_ERROR", + 0x55 => "TIMEOUT", + 0x56 => "AUTH_UNKNOWN", + 0x57 => "FILTER_ERROR", + 0x58 => "USER_CANCELLED", + 0x59 => "PARAM_ERROR", + 0x5a => "NO_MEMORY", + + 0x5b => "CONNECT_ERROR", + 0x5c => "NOT_SUPPORTED", + 0x5d => "CONTROL_NOT_FOUND", + 0x5e => "NO_RESULTS_RETURNED", + 0x5f => "MORE_RESULTS_TO_RETURN", + 0x60 => "CLIENT_LOOP", + 0x61 => "REFERRAL_LIMIT_EXCEEDED", + }.each do |code, name| + IMPLEMENT_SPECIFIC_ERRORS[code] = + ActiveLdap::LdapError.define(code, name, self) + end + + class Conn + def failed? + not err.zero? + end + + def error_message + if failed? + LDAP.err2string(err) + else + nil + end + end + + def assert_error_code + return unless failed? + klass = ActiveLdap::LdapError::ERRORS[err] + klass ||= IMPLEMENT_SPECIFIC_ERRORS[err] + klass ||= ActiveLdap::LdapError + raise klass, LDAP.err2string(err) + end + end +end Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Sun May 6 23:18:41 2007 @@ -129,7 +129,7 @@ class EntryInvalid < Error end - class UnwillingToPerform < Error + class OperationNotPermitted < Error end class ConnectionNotEstablished < Error @@ -138,6 +138,14 @@ class AdapterNotSpecified < Error end + class AdapterNotFound < Error + attr_reader :adapter + def initialize(adapter) + @adapter = adapter + super("LDAP configuration specifies nonexistent #{@adapter} adapter") + end + end + class UnknownAttribute < Error attr_reader :name def initialize(name) @@ -1483,13 +1491,9 @@ prepare_data_for_saving do |data, ldap_data| entries = collect_all_entries(data) logger.debug {"#create: adding #{dn}"} - begin - self.class.add(dn, entries) - logger.debug {"#create: add successful"} - @new_entry = false - rescue UnwillingToPerform - logger.warn {"#create: didn't perform: #{$!.message}"} - end + self.class.add(dn, entries) + logger.debug {"#create: add successful"} + @new_entry = false true end end Modified: trunk/lib/active_ldap/connection.rb ============================================================================== --- trunk/lib/active_ldap/connection.rb (original) +++ trunk/lib/active_ldap/connection.rb Sun May 6 23:18:41 2007 @@ -51,9 +51,14 @@ active_connections[active_connection_name] = adapter elsif adapter.is_a?(Hash) config = adapter - adapter = Inflector.camelize(config[:adapter] || "ldap") + adapter = (config[:adapter] || "ldap") + normalized_adapter = adapter.downcase.gsub(/-/, "_") + adapter_method = "#{normalized_adapter}_connection" + unless Adapter::Base.respond_to?(adapter_method) + raise AdapterNotFound.new(adapter) + end config = remove_connection_related_configuration(config) - self.connection = Adapter.const_get(adapter).new(config) + self.connection = Adapter::Base.send(adapter_method, config) elsif adapter.nil? raise ConnectionNotEstablished else Added: trunk/lib/active_ldap/ldap_error.rb ============================================================================== --- (empty file) +++ trunk/lib/active_ldap/ldap_error.rb Sun May 6 23:18:41 2007 @@ -0,0 +1,74 @@ +module ActiveLdap + class LdapError < Error + class << self + def define(code, name, target) + klass_name = name.downcase.camelize + target.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + class #{klass_name} < #{self} + CODE = #{code} + def code + CODE + end + end +EOC + target.const_get(klass_name) + end + end + + ERRORS = {} + { + 0x00 => "SUCCESS", + 0x01 => "OPERATIONS_ERROR", + 0x02 => "PROTOCOL_ERROR", + 0x03 => "TIMELIMIT_EXCEEDED", + 0x04 => "SIZELIMIT_EXCEEDED", + 0x05 => "COMPARE_FALSE", + 0x06 => "COMPARE_TRUE", + 0x07 => "AUTH_METHOD_NOT_SUPPORTED", + 0x08 => "STRONG_AUTH_REQUIRED", + 0x09 => "PARTIAL_RESULTS", # LDAPv2+ (not LDAPv3) + + 0x0a => "REFERRAL", + 0x0b => "ADMINLIMIT_EXCEEDED", + 0x0c => "UNAVAILABLE_CRITICAL_EXTENSION", + 0x0d => "CONFIDENTIALITY_REQUIRED", + 0x0e => "LDAP_SASL_BIND_IN_PROGRESS", + + 0x10 => "NO_SUCH_ATTRIBUTE", + 0x11 => "UNDEFINED_TYPE", + 0x12 => "INAPPROPRIATE_MATCHING", + 0x13 => "CONSTRAINT_VIOLATION", + 0x14 => "TYPE_OR_VALUE_EXISTS", + 0x15 => "INVALID_SYNTAX", + + 0x20 => "NO_SUCH_OBJECT", + 0x21 => "ALIAS_PROBLEM", + 0x22 => "INVALID_DN_SYNTAX", + 0x23 => "IS_LEAF", + 0x24 => "ALIAS_DEREF_PROBLEM", + + 0x2F => "PROXY_AUTHZ_FAILURE", + 0x30 => "INAPPROPRIATE_AUTH", + 0x31 => "INVALID_CREDENTIALS", + 0x32 => "INSUFFICIENT_ACCESS", + + 0x33 => "BUSY", + 0x34 => "UNAVAILABLE", + 0x35 => "UNWILLING_TO_PERFORM", + 0x36 => "LOOP_DETECT", + + 0x40 => "NAMING_VIOLATION", + 0x41 => "OBJECT_CLASS_VIOLATION", + 0x42 => "NOT_ALLOWED_ON_NONLEAF", + 0x43 => "NOT_ALLOWED_ON_RDN", + 0x44 => "ALREADY_EXISTS", + 0x45 => "NO_OBJECT_CLASS_MODS", + 0x46 => "RESULTS_TOO_LARGE", + 0x47 => "AFFECTS_MULTIPLE_DSAS", + + 0x50 => "OTHER", + }.each do |code, name| + ERRORS[code] = LdapError.define(code, name, self) + end + end +end Modified: trunk/test/al-test-utils.rb ============================================================================== --- trunk/test/al-test-utils.rb (original) +++ trunk/test/al-test-utils.rb Sun May 6 23:18:41 2007 @@ -113,7 +113,10 @@ next if dc_class.exists?(value, :prefix => "dc=#{value}") dc = dc_class.new(value) dc.o = dc.dc - dc.save + begin + dc.save + rescue ActiveLdap::OperationNotPermitted + end end end From codesite-noreply at google.com Tue May 8 00:07:22 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:07:22 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r168 - trunk/lib/active_ldap/adapter Message-ID: <163600d103042fed8df394589f6b1@google.com> Author: koutou Date: Mon May 7 21:07:07 2007 New Revision: 168 Modified: trunk/lib/active_ldap/adapter/ldap.rb Log: * improved error message. Modified: trunk/lib/active_ldap/adapter/ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap.rb (original) +++ trunk/lib/active_ldap/adapter/ldap.rb Mon May 7 21:07:07 2007 @@ -304,7 +304,7 @@ } value = scope_map[scope || :sub] if value.nil? - available_scopes = scope_map.keys.collect {|s| s.inspect} + available_scopes = scope_map.keys.inspect raise ArgumentError, "#{scope.inspect} is not one of the available " + "LDAP scope #{available_scopes}" end From codesite-noreply at google.com Tue May 8 00:08:37 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:08:37 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r169 - trunk/lib/active_ldap/adapter Message-ID: <163600d8e4042fed926e6e879fbdd@google.com> Author: koutou Date: Mon May 7 21:08:20 2007 New Revision: 169 Modified: trunk/lib/active_ldap/adapter/ldap_ext.rb Log: * used require_library_or_gem instead of require. Modified: trunk/lib/active_ldap/adapter/ldap_ext.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap_ext.rb (original) +++ trunk/lib/active_ldap/adapter/ldap_ext.rb Mon May 7 21:08:20 2007 @@ -1,4 +1,4 @@ -require 'ldap' +require_library_or_gem 'ldap' require 'ldap/ldif' require 'ldap/schema' From codesite-noreply at google.com Tue May 8 00:10:27 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:10:27 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r170 - trunk/lib/active_ldap/adapter Message-ID: <163600d103042fed98f946fb9f8bf@google.com> Author: koutou Date: Mon May 7 21:10:11 2007 New Revision: 170 Modified: trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/adapter/ldap.rb Log: * implemented convenience search filter notation. But this implementation is too dirty!!! Should write tests and re-write code. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Mon May 7 21:10:11 2007 @@ -60,6 +60,46 @@ raise TimeoutError, e.message end end + + # FIXME: should cleanup!!! + def parse_filter(filter) + return nil if filter.nil? + if !filter.is_a?(String) and !filter.respond_to?(:collect) + filter = filter.to_s + end + + if filter.is_a?(String) + if filter.empty? + nil + else + filter + end + else + type, *components = filter + unless [:and, :or, :&, :|].include?(type) + components.unshift(type) + type = :and + end + if type == :& + type = :and + elsif type == :| + type = :or + end + + components = components.collect do |component| + parse_filter(component) + end.compact + + case components.size + when 0 + nil + when 1 + components.join + else + "(#{type == :and ? '&' : '|'}#{components.join})" + end + end + end end end end Modified: trunk/lib/active_ldap/adapter/ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap.rb (original) +++ trunk/lib/active_ldap/adapter/ldap.rb Mon May 7 21:10:11 2007 @@ -150,7 +150,7 @@ # Wraps Ruby/LDAP connection.search to make it easier to search for # specific data without cracking open Base.connection def search(options={}) - filter = options[:filter] || 'objectClass=*' + filter = parse_filter(options[:filter] || 'objectClass=*') attrs = options[:attributes] || [] scope = ensure_scope(options[:scope]) base = options[:base] From codesite-noreply at google.com Tue May 8 00:14:35 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:14:35 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r171 - trunk/lib/active_ldap/association Message-ID: Author: koutou Date: Mon May 7 21:14:18 2007 New Revision: 171 Modified: trunk/lib/active_ldap/association/belongs_to.rb trunk/lib/active_ldap/association/belongs_to_many.rb trunk/lib/active_ldap/association/has_many.rb trunk/lib/active_ldap/association/has_many_wrap.rb Log: * used convenience filter notification. Modified: trunk/lib/active_ldap/association/belongs_to.rb ============================================================================== --- trunk/lib/active_ldap/association/belongs_to.rb (original) +++ trunk/lib/active_ldap/association/belongs_to.rb Mon May 7 21:14:18 2007 @@ -28,7 +28,9 @@ end def find_target - filter = "(#{primary_key}=#{@owner[@options[:foreign_key_name]]})" + value = @owner[@options[:foreign_key_name]] + raise EntryNotFound if value.nil? + filter = "(#{primary_key}=#{value})" result = foreign_class.find(:all, :filter => filter, :limit => 1) raise EntryNotFound if result.empty? result.first Modified: trunk/lib/active_ldap/association/belongs_to_many.rb ============================================================================== --- trunk/lib/active_ldap/association/belongs_to_many.rb (original) +++ trunk/lib/active_ldap/association/belongs_to_many.rb Mon May 7 21:14:18 2007 @@ -28,12 +28,11 @@ def find_target key = @options[:many] - filter = @owner[@options[:foreign_key_name], true].reject do |value| - value.nil? - end.collect do |value| + values = @owner[@options[:foreign_key_name], true].compact + components = values.collect do |value| "(#{key}=#{value})" - end.join - foreign_class.find(:all, :filter => "(|#{filter})") + end + foreign_class.find(:all, :filter => [:or, *components]) end end end Modified: trunk/lib/active_ldap/association/has_many.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many.rb (original) +++ trunk/lib/active_ldap/association/has_many.rb Mon May 7 21:14:18 2007 @@ -11,7 +11,7 @@ def find_target foreign_base_key = primary_key - filter = @owner[@options[:foreign_key_name], true].collect do |value| + components = @owner[@options[:foreign_key_name], true].collect do |value| key = val = nil if foreign_base_key == "dn" key, val = value.split(",")[0].split("=") unless value.empty? @@ -23,25 +23,25 @@ key.nil? or val.nil? end.collect do |key, val| "(#{key}=#{val})" - end.join - foreign_class.find(:all, :filter => "(|#{filter})") + end + foreign_class.find(:all, :filter => [:or, *components]) end def delete_entries(entries) key = primary_key dn_attribute = foreign_class.dn_attribute - filter = @owner[@options[:foreign_key_name], true].reject do |value| + components = @owner[@options[:foreign_key_name], true].reject do |value| value.nil? end.collect do |value| "(#{key}=#{value})" - end.join - filter = "(&#{filter})" - entry_filter = entries.collect do |entry| + end + filter = [:and, *components] + entry_components = entries.collect do |entry| "(#{dn_attribute}=#{entry.id})" - end.join - entry_filter = "(|#{entry_filter})" + end + entry_filter = [:or, *entry_components] foreign_class.update_all({primary_key => []}, - "(&#{filter}#{entry_filter})") + [:and, filter, entry_filter]) end end end Modified: trunk/lib/active_ldap/association/has_many_wrap.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many_wrap.rb (original) +++ trunk/lib/active_ldap/association/has_many_wrap.rb Mon May 7 21:14:18 2007 @@ -27,7 +27,7 @@ foreign_base_key = primary_key requested_targets = @owner[@options[:wrap], true] - filter = requested_targets.collect do |value| + components = requested_targets.collect do |value| key = val = nil if foreign_base_key == "dn" key, val = value.split(",")[0].split("=") unless value.empty? @@ -39,11 +39,11 @@ key.nil? or val.nil? end.collect do |key, val| "(#{key}=#{val})" - end.join + end klass = foreign_class found_targets = {} - klass.find(:all, :filter => "(|#{filter})").each do |target| + klass.find(:all, :filter => [:or, *components]).each do |target| found_targets[target.send(foreign_base_key)] ||= target end From codesite-noreply at google.com Tue May 8 00:30:35 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:30:35 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r172 - trunk/lib/active_ldap/association Message-ID: <163600d8e4042fede0fda4e0a077d@google.com> Author: koutou Date: Mon May 7 21:30:18 2007 New Revision: 172 Modified: trunk/lib/active_ldap/association/has_many_wrap.rb Log: * don't call search if it's not needed. Modified: trunk/lib/active_ldap/association/has_many_wrap.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many_wrap.rb (original) +++ trunk/lib/active_ldap/association/has_many_wrap.rb Mon May 7 21:30:18 2007 @@ -27,7 +27,7 @@ foreign_base_key = primary_key requested_targets = @owner[@options[:wrap], true] - components = requested_targets.collect do |value| + component_targets = requested_targets.collect do |value| key = val = nil if foreign_base_key == "dn" key, val = value.split(",")[0].split("=") unless value.empty? @@ -37,7 +37,10 @@ [key, val] end.reject do |key, val| key.nil? or val.nil? - end.collect do |key, val| + end + return [] if component_targets.empty? + + components = component_targets.collect do |key, val| "(#{key}=#{val})" end From codesite-noreply at google.com Tue May 8 00:33:18 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:33:18 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r173 - trunk/lib/active_ldap Message-ID: <163600d8e4042fedeabeb178a08f7@google.com> Author: koutou Date: Mon May 7 21:33:03 2007 New Revision: 173 Modified: trunk/lib/active_ldap/base.rb Log: * supported convenience filter notification. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Mon May 7 21:33:03 2007 @@ -487,10 +487,10 @@ def update_all(attributes, filter=nil, options={}) search_options = options if filter - if /[=\(\)&\|]/ =~ filter - search_options = search_options.merge(:filter => filter) - else + if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter search_options = search_options.merge(:value => filter) + else + search_options = search_options.merge(:filter => filter) end end targets = search(search_options).collect do |dn, attrs| @@ -1076,8 +1076,10 @@ def extract_object_class(attributes) classes = [] attrs = attributes.reject do |key, value| - if key.to_s == 'objectClass' or - Inflector.underscore(key) == 'object_class' + key = key.to_s + if key == 'objectClass' or + key.underscore == 'object_class' or + key.downcase == 'objectclass' classes |= [value].flatten true else From codesite-noreply at google.com Tue May 8 00:34:34 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:34:34 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r174 - in trunk: examples lib lib/active_ldap/adapter test Message-ID: <163600d406042fedef3db488a03f0@google.com> Author: koutou Date: Mon May 7 21:34:17 2007 New Revision: 174 Added: trunk/lib/active_ldap/adapter/net_ldap.rb trunk/lib/active_ldap/adapter/net_ldap_ext.rb Modified: trunk/examples/config.yaml.example trunk/lib/active_ldap.rb trunk/test/config.yaml.sample Log: * supported Net::LDAP but it's too dirty!!! Should fix. Modified: trunk/examples/config.yaml.example ============================================================================== --- trunk/examples/config.yaml.example (original) +++ trunk/examples/config.yaml.example Mon May 7 21:34:17 2007 @@ -2,3 +2,4 @@ method: :tls base: dc=localdomain bind_dn: cn=admin,dc=localdomain +#adapter: net-ldap Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Mon May 7 21:34:17 2007 @@ -959,7 +959,7 @@ end unless defined?(ACTIVE_LDAP_CONNECTION_ADAPTERS) - ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap) + ACTIVE_LDAP_CONNECTION_ADAPTERS = %w(ldap net_ldap) end ACTIVE_LDAP_CONNECTION_ADAPTERS.each do |adapter| Added: trunk/lib/active_ldap/adapter/net_ldap.rb ============================================================================== --- (empty file) +++ trunk/lib/active_ldap/adapter/net_ldap.rb Mon May 7 21:34:17 2007 @@ -0,0 +1,439 @@ +require 'active_ldap/adapter/base' + +module ActiveLdap + module Adapter + class Base + class << self + def net_ldap_connection(options) + unless defined?(::Net::LDAP) + require 'active_ldap/adapter/net_ldap_ext' + end + NetLdap.new(options) + end + end + end + + class NetLdap < Base + METHOD = { + :ssl => :simple_tls, + :tls => :start_tls, + :plain => nil, + } + + def connect(options={}) + @bound = false + + method = ensure_method(options[:method] || @method) + host = options[:host] || @host + port = options[:port] || @port + + config = { + :host => host, + :port => port, + :encryption => {:method => method}, + } + @connection = Net::LDAP::Connection.new(config) + bind(options) + end + + def schema(options={}) + @schema ||= operation(options) do + base = options[:base] + attrs = options[:attributes] + + key = 'subschemaSubentry' + attrs ||= [ + 'objectClasses', + 'attributeTypes', + 'matchingRules', + 'matchingRuleUse', + 'dITStructureRules', + 'dITContentRules', + 'nameForms', + 'ldapSyntaxes', + #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER. + ] + base ||= root_dse([key])[key][0] + base ||= 'cn=schema' + result = search(:base => base, + :scope => :base, + :filter => '(objectClass=subschema)', + :attributes => attrs)[0][1] + Schema.new(result) + end +# rescue +# raise ConnectionError.new("Unable to retrieve schema from " + +# "server (#{@method.class.downcase})") + end + + def disconnect!(options={}) + return if @connection.nil? + unbind(options) + @connection = nil + end + + def unbind(options={}) + @bound = false + end + + def rebind(options={}) + unbind(options) if bound? + connect(options) + end + + def bind(options={}) + bind_dn = options[:bind_dn] || @bind_dn + try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl + if options.has_key?(:allow_anonymous) + allow_anonymous = options[:allow_anonymous] + else + allow_anonymous = @allow_anonymous + end + + begin + # Rough bind loop: + # Attempt 1: SASL if available + # Attempt 2: SIMPLE with credentials if password block + # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') + if try_sasl and sasl_bind(bind_dn, options) + @bound = true + @logger.info {'Bound SASL'} + elsif simple_bind(bind_dn, options) + @bound = true + @logger.info {'Bound simple'} + elsif allow_anonymous and bind_as_anonymous(options) + @bound = true + @logger.info {'Bound anonymous'} + else + @bound = false + raise AuthenticationError, 'All authentication methods exhausted.' + end + rescue Net::LDAP::LdapError + raise AuthenticationError, $!.message + end + + bound? + end + + def bind_as_anonymous(options={}) + @logger.info {"Attempting anonymous authentication"} + operation(options) do + (@connection.bind :method => :anonymous).zero? + end + end + + def connecting? + not @connection.nil? + end + + def bound? + connecting? and @bound + end + + def search(options={}) + filter = parse_filter(options[:filter] || 'objectClass=*') + attrs = options[:attributes] || [] + scope = ensure_scope(options[:scope]) + base = options[:base] + limit = options[:limit] || 0 + limit = nil if limit <= 0 + + results = [] + attrs = attrs.to_a # just in case + + begin + operation(options) do + args = { + :base => base, + :scope => scope, + :filter => filter, + :attributes => attrs, + :size => limit, + } + execute(:search, args) do |entry| + attributes = {} + entry.original_attribute_names.each do |name| + attributes[name] = entry[name] + end + value = [entry.dn, attributes] + value = yield(value) if block_given? + results.push(value) + end + end + rescue LdapError + # Do nothing on failure + @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " + + "for #{filter} and attrs #{attrs.inspect}"} + end + + results + end + + def to_ldif(dn, attributes) + entry = Net::LDAP::Entry.new(dn.dup) + attributes.each do |key, values| + entry[key] = values.flatten + end + entry.to_ldif + end + + def load(ldifs, options={}) + operation(options) do + ldifs.split(/(?:\r?\n){2,}/).each do |ldif| + entry = Net::LDAP::Entry.from_single_ldif_string(ldif) + attributes = {} + entry.each do |name, values| + attributes[name] = values + end + attributes.delete(:dn) + execute(:add, + :dn => entry.dn, + :attributes => attributes) + end + end + end + + def delete(targets, options={}) + targets = [targets] unless targets.is_a?(Array) + return if targets.empty? + target = nil + begin + operation(options) do + targets.each do |target| + execute(:delete, :dn => target) + end + end + rescue LdapError::NoSuchObject + raise EntryNotFound, "No such entry: #{target}" + end + end + + def add(dn, entries, options={}) + begin + operation(options) do + attributes = {} + entries.each do |type, key, attrs| + attrs.each do |name, values| + attributes[name] = values + end + end + execute(:add, + :dn => dn, + :attributes => attributes) + end + rescue LdapError::NoSuchObject + raise EntryNotFound, "No such entry: #{dn}" + rescue LdapError::InvalidDnSyntax + raise DistinguishedNameInvalid.new(dn) + rescue LdapError::AlreadyExists + raise EntryAlreadyExist, "#{$!.message}: #{dn}" + rescue LdapError::StrongAuthRequired + raise StrongAuthenticationRequired, "#{$!.message}: #{dn}" + rescue LdapError::ObjectClassViolation + raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + rescue LdapError::UnwillingToPerform + raise OperationNotPermitted, "#{$!.message}: #{dn}" + end + end + + def modify(dn, entries, options={}) + begin + operation(options) do + execute(:modify, + :dn => dn, + :operations => parse_entries(entries)) + end + rescue LdapError::UndefinedType + raise + rescue LdapError::ObjectClassViolation + raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + end + end + + private + def operation(options={}, &block) + reconnect_if_need + try_reconnect = !options.has_key?(:try_reconnect) || + options[:try_reconnect] + with_timeout(try_reconnect, options) do + block.call + end + end + + def execute(method, *args, &block) + result = @connection.send(method, *args, &block) + message = nil + if result.is_a?(Hash) + message = result[:errorMessage] + result = result[:resultCode] + end + unless result.zero? + klass = LdapError::ERRORS[result] + klass ||= LdapError + raise klass, + [Net::LDAP.result2string(result), message].compact.join(": ") + end + end + + def root_dse(attrs) + entry = search(:base => "", + :scope => :base, + :attributes => attrs).first + dn, attributes = entry + attributes + end + + def ensure_method(method) + method ||= "plain" + normalized_method = method.to_s.downcase.to_sym + return METHOD[normalized_method] if METHOD.has_key?(normalized_method) + + available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ") + raise ConfigurationError, + "#{method.inspect} is not one of the available connect " + + "methods #{available_methods}" + end + + def ensure_scope(scope) + scope_map = { + :base => Net::LDAP::SearchScope_BaseObject, + :sub => Net::LDAP::SearchScope_WholeSubtree, + :one => Net::LDAP::SearchScope_SingleLevel, + } + value = scope_map[scope || :sub] + if value.nil? + available_scopes = scope_map.keys.inspect + raise ArgumentError, "#{scope.inspect} is not one of the available " + + "LDAP scope #{available_scopes}" + end + value + end + + # Bind to LDAP with the given DN using any available SASL methods + def sasl_bind(bind_dn, options={}) + return false unless bind_dn + + # Get all SASL mechanisms + mechanisms = operation(options) do + key = "supportedSASLMechanisms" + root_dse([key])[key] + end + mechanisms ||= [] + + sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms + sasl_mechanisms.each do |mechanism| + next unless mechanisms.include?(mechanism) + operation(options) do + args = { + :method => :sasl, + :initial_credential => bind_dn, + :mechanism => mechanism, + } + if need_credential_sasl_mechanism?(mechanism) + args[:challenge_response] = Proc.new do |cred| + password(cred, options) + end + end + @connection.bind(args) + return true if @connection.bound? + end + end + false + end + + # Bind to LDAP with the given DN and password + def simple_bind(bind_dn, options={}) + return false unless bind_dn + + passwd = password(bind_dn, options) + return false unless passwd + + begin + operation do + args = { + :method => :simple, + :username => bind_dn, + :password => passwd, + } + @connection.bind(args).zero? + end + rescue Net::LDAP::LdapError + @logger.debug {"Failed to bind as DN: #{bind_dn}"} + false + end + end + + def parse_entries(entries) + result = [] + entries.each do |type, key, attributes| + mod_type = ensure_mod_type(type) + attributes.each do |name, values| + result << [mod_type, name, values] + end + end + result + end + + def ensure_mod_type(type) + case type + when :replace, :add + type + else + raise ArgumentError, "unknown type: #{type}" + end + end + + # Attempts to reconnect up to the number of times allowed + # If forced, try once then fail with ConnectionError if not connected. + def reconnect(options={}) + options = options.dup + force = options[:force] + retry_limit = options[:retry_limit] || @retry_limit + retry_wait = options[:retry_wait] || @retry_wait + options[:reconnect_attempts] ||= 0 + + loop do + unless can_reconnect?(options) + raise ConnectionError, + 'Giving up trying to reconnect to LDAP server.' + end + + @logger.debug {'Attempting to reconnect'} + disconnect! + + # Reset the attempts if this was forced. + options[:reconnect_attempts] = 0 if force + options[:reconnect_attempts] += 1 if retry_limit >= 0 + begin + connect(options) + break + rescue => detail + @logger.error {"Reconnect to server failed: #{detail.exception}"} + @logger.error {"Reconnect to server failed backtrace:\n" + + detail.backtrace.join("\n")} + # Do not loop if forced + raise ConnectionError, detail.message if force + end + + # Sleep before looping + sleep retry_wait + end + + true + end + + def reconnect_if_need(options={}) + reconnect(options) if !connecting? and can_reconnect?(options) + end + + # Determine if we have exceed the retry limit or not. + # True is reconnecting is allowed - False if not. + def can_reconnect?(options={}) + retry_limit = options[:retry_limit] || @retry_limit + reconnect_attempts = options[:reconnect_attempts] || 0 + + retry_limit < 0 or reconnect_attempts < (retry_limit - 1) + end + end + end +end Added: trunk/lib/active_ldap/adapter/net_ldap_ext.rb ============================================================================== --- (empty file) +++ trunk/lib/active_ldap/adapter/net_ldap_ext.rb Mon May 7 21:34:17 2007 @@ -0,0 +1,29 @@ +require_library_or_gem 'net/ldap' + +module Net + class LDAP + class Entry + alias initialize_without_original_attribute_names initialize + def initialize(*args) + @original_attribute_names = [] + initialize_without_original_attribute_names(*args) + end + + alias aset_without_original_attribute_names []= + def []=(name, value) + @original_attribute_names << name + aset_without_original_attribute_names(name, value) + end + + def original_attribute_names + @original_attribute_names.compact.uniq + end + + def each_attribute + attribute_names.sort_by {|name| name.to_s}.each do |name| + yield name, self[name] + end + end + end + end +end Modified: trunk/test/config.yaml.sample ============================================================================== --- trunk/test/config.yaml.sample (original) +++ trunk/test/config.yaml.sample Mon May 7 21:34:17 2007 @@ -4,3 +4,4 @@ method: :tls bind_dn: cn=user-name,dc=localdomain password: secret +# adapter: net-ldap From codesite-noreply at google.com Tue May 8 00:35:15 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:35:15 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r175 - trunk/lib Message-ID: Author: koutou Date: Mon May 7 21:35:00 2007 New Revision: 175 Modified: trunk/lib/active_ldap.rb Log: * removed needless $VERBOSE trick. Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Mon May 7 21:35:00 2007 @@ -907,9 +907,6 @@ # package, and I'd like to see it prove helpful to more people than just myself. # -# Blanket warning hiding. Remove for debugging -$VERBOSE, verbose = false, $VERBOSE - if RUBY_PLATFORM.match('linux') require 'active_ldap/timeout' else @@ -965,5 +962,3 @@ ACTIVE_LDAP_CONNECTION_ADAPTERS.each do |adapter| require "active_ldap/adapter/#{adapter}" end - -$VERBOSE = verbose From codesite-noreply at google.com Tue May 8 00:57:22 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 07 May 2007 21:57:22 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r176 - trunk/test Message-ID: Author: koutou Date: Mon May 7 21:57:07 2007 New Revision: 176 Modified: trunk/test/al-test-utils.rb trunk/test/run-test.rb Log: * run all adapters' test. Modified: trunk/test/al-test-utils.rb ============================================================================== --- trunk/test/al-test-utils.rb (original) +++ trunk/test/al-test-utils.rb Mon May 7 21:57:07 2007 @@ -48,7 +48,12 @@ unless File.exist?(@config_file) raise "config file for testing doesn't exist: #{@config_file}" end - YAML.load(ERB.new(File.read(@config_file)).result) + config = YAML.load(ERB.new(File.read(@config_file)).result) + config.each do |key, value| + adapter = ENV["ACTIVE_LDAP_TEST_ADAPTER"] + value[:adapter] = adapter if adapter + end + config end end Modified: trunk/test/run-test.rb ============================================================================== --- trunk/test/run-test.rb (original) +++ trunk/test/run-test.rb Mon May 7 21:57:07 2007 @@ -10,8 +10,20 @@ require 'test-unit-ext' -if Test::Unit::AutoRunner.respond_to?(:standalone?) - exit Test::Unit::AutoRunner.run($0, File.dirname($0)) -else - exit Test::Unit::AutoRunner.run(false, File.dirname($0)) +test_file = "test/test_*.rb" +Dir.glob(test_file) do |file| + require file +end + +[nil, "ldap", "net-ldap"].each do |adapter| + ENV["ACTIVE_LDAP_TEST_ADAPTER"] = adapter + puts "using adapter: #{adapter ? adapter : 'default'}" + args = [File.dirname($0), ARGV.dup] + if Test::Unit::AutoRunner.respond_to?(:standalone?) + args.unshift(false) + else + args.unshift($0) + end + Test::Unit::AutoRunner.run(*args) + puts end From codesite-noreply at google.com Wed May 9 04:04:10 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Wed, 09 May 2007 01:04:10 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r177 - in trunk: lib/active_ldap/adapter test Message-ID: <163600d685043004faa76aade0b7@google.com> Author: koutou Date: Wed May 9 01:03:52 2007 New Revision: 177 Added: trunk/test/test_adapter.rb Modified: trunk/lib/active_ldap/adapter/base.rb Log: * cleanup ActiveLdap::Adapter::Base#parse_filter. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Wed May 9 01:03:52 2007 @@ -61,43 +61,89 @@ end end - # FIXME: should cleanup!!! def parse_filter(filter) return nil if filter.nil? if !filter.is_a?(String) and !filter.respond_to?(:collect) filter = filter.to_s end - if filter.is_a?(String) - if filter.empty? - nil - else - filter + case filter + when String + parse_filter_string(filter) + when Hash + components = filter.sort_by {|k, v| k.to_s}.collect do |key, value| + construct_component(key, value) end + construct_filter(components) else - type, *components = filter - unless [:and, :or, :&, :|].include?(type) - components.unshift(type) - type = :and + operator, *components = filter + unless operator.is_a?(Symbol) + components.unshift(operator) + operator = nil end - if type == :& - type = :and - elsif type == :| - type = :or + + components = components.collect do |key, value, *others| + if value.nil? + parse_filter(key) + elsif !others.empty? + parse_filter([key, value, *others]) + else + construct_component(key, value) + end end + construct_filter(components, operator) + end + end - components = components.collect do |component| - parse_filter(component) - end.compact - - case components.size - when 0 - nil - when 1 - components.join + def parse_filter_string(filter) + if /\A\s*\z/.match(filter) + nil + else + if filter[0, 1] == "(" + filter else - "(#{type == :and ? '&' : '|'}#{components.join})" + "(#{filter})" + end + end + end + + def construct_component(key, value) + if !value.is_a?(String) and value.respond_to?(:collect) + values = value.collect {|v| [key, v]} + if values[0][1].is_a?(Symbol) + _, operator = values.shift + values.unshift(operator) end + parse_filter(values) + else + "(#{key}=#{value})" + end + end + + def construct_filter(components, operator=nil) + operator = normalize_filter_logical_operator(operator) + components = components.compact + case components.size + when 0 + nil + when 1 + components.join + else + "(#{operator == :and ? '&' : '|'}#{components.join})" + end + end + + def normalize_filter_logical_operator(type) + case (type || :and) + when :and, :& + :and + when :or, :| + :or + else + operators = [:and, :or, :&, :|] + raise ArgumentError, + "invalid logical operator: #{type.inspect}: " + + "available operators: #{operators.inspect}" end end end Added: trunk/test/test_adapter.rb ============================================================================== --- (empty file) +++ trunk/test/test_adapter.rb Wed May 9 01:03:52 2007 @@ -0,0 +1,76 @@ +require 'al-test-utils' + +class TestAdapter < Test::Unit::TestCase + include AlTestUtils + + def setup + end + + def teardown + end + + priority :must + + priority :normal + def test_empty_filter + assert_parse_filter(nil, nil) + assert_parse_filter(nil, "") + assert_parse_filter(nil, " ") + end + + def test_simple_filter + assert_parse_filter("(objectClass=*)", "objectClass=*") + assert_parse_filter("(objectClass=*)", "(objectClass=*)") + assert_parse_filter("(&(uid=bob)(objectClass=*))", + "(&(uid=bob)(objectClass=*))") + + assert_parse_filter("(objectClass=*)", {:objectClass => "*"}) + assert_parse_filter("(&(objectClass=*)(uid=bob))", + {:uid => "bob", :objectClass => "*"}) + + assert_parse_filter("(&(uid=bob)(objectClass=*))", + [:and, "uid=bob", "objectClass=*"]) + assert_parse_filter("(&(uid=bob)(objectClass=*))", + [:&, "uid=bob", "objectClass=*"]) + assert_parse_filter("(|(uid=bob)(objectClass=*))", + [:or, "uid=bob", "objectClass=*"]) + assert_parse_filter("(|(uid=bob)(objectClass=*))", + [:|, "uid=bob", "objectClass=*"]) + end + + def test_multi_value_filter + assert_parse_filter("(&(objectClass=top)(objectClass=posixAccount))", + {:objectClass => ["top", "posixAccount"]}) + + assert_parse_filter("(&(objectClass=top)(objectClass=posixAccount))", + [[:objectClass, "top"], + [:objectClass, "posixAccount"]]) + assert_parse_filter("(&(objectClass=top)(objectClass=posixAccount))", + [[:objectClass, ["top", "posixAccount"]]]) + end + + def test_nested_filter + assert_parse_filter("(&(objectClass=*)(uid=bob))", + [:and, {:uid => "bob", :objectClass => "*"}]) + assert_parse_filter("(&(objectClass=*)(|(uid=bob)(uid=alice)))", + [:and, {:objectClass => "*"}, + [:or, [:uid, "bob"], [:uid, "alice"]]]) + assert_parse_filter("(&(objectClass=*)(|(uid=bob)(uid=alice)))", + [:and, + {:objectClass => "*", + :uid => [:or, "bob", "alice"]}]) + end + + def test_invalid_operator + assert_raises(ArgumentError) do + assert_parse_filter("(&(objectClass=*)(uid=bob))", + [:xxx, {:uid => "bob", :objectClass => "*"}]) + end + end + + private + def assert_parse_filter(expected, filter) + adapter = ActiveLdap::Adapter::Base.new + assert_equal(expected, adapter.send(:parse_filter, filter)) + end +end From codesite-noreply at google.com Wed May 9 22:51:39 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Wed, 09 May 2007 19:51:39 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r178 - in trunk: lib/active_ldap/adapter lib/active_ldap/association test Message-ID: Author: koutou Date: Wed May 9 19:51:17 2007 New Revision: 178 Modified: trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/association/belongs_to.rb trunk/lib/active_ldap/association/belongs_to_many.rb trunk/lib/active_ldap/association/has_many.rb trunk/lib/active_ldap/association/has_many_wrap.rb trunk/test/test_adapter.rb Log: * fixed filter bugs. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Wed May 9 19:51:17 2007 @@ -61,7 +61,7 @@ end end - def parse_filter(filter) + def parse_filter(filter, operator=nil) return nil if filter.nil? if !filter.is_a?(String) and !filter.respond_to?(:collect) filter = filter.to_s @@ -72,23 +72,25 @@ parse_filter_string(filter) when Hash components = filter.sort_by {|k, v| k.to_s}.collect do |key, value| - construct_component(key, value) + construct_component(key, value, operator) end - construct_filter(components) + construct_filter(components, operator) else - operator, *components = filter - unless operator.is_a?(Symbol) - components.unshift(operator) - operator = nil - end + operator, components = normalize_array_filter(filter, operator) - components = components.collect do |key, value, *others| - if value.nil? - parse_filter(key) - elsif !others.empty? - parse_filter([key, value, *others]) + components = components.collect do |component| + if component.is_a?(Array) and component.size == 2 + key, value = component + if filter_logical_operator?(key) or value.is_a?(Hash) + parse_filter(value, key) + else + construct_component(key, value, operator) + end + elsif component.is_a?(Symbol) + assert_filter_logical_operator(component) + nil else - construct_component(key, value) + parse_filter(component, operator) end end construct_filter(components, operator) @@ -107,14 +109,28 @@ end end - def construct_component(key, value) - if !value.is_a?(String) and value.respond_to?(:collect) - values = value.collect {|v| [key, v]} - if values[0][1].is_a?(Symbol) - _, operator = values.shift - values.unshift(operator) + def normalize_array_filter(filter, operator=nil) + filter_operator, *components = filter + if filter_logical_operator?(filter_operator) + operator = filter_operator + else + components.unshift(filter_operator) + end + [operator, components] + end + + def construct_component(key, value, operator=nil) + if collection?(value) + values = [] + value.each do |val| + if collection?(val) + values.concat(val.collect {|v| [key, v]}) + else + values << [key, val] + end end - parse_filter(values) + values[0] = values[0][1] if filter_logical_operator?(values[0][1]) + parse_filter(values, operator) else "(#{key}=#{value})" end @@ -133,17 +149,31 @@ end end - def normalize_filter_logical_operator(type) - case (type || :and) + def collection?(object) + !object.is_a?(String) and object.respond_to?(:each) + end + + LOGICAL_OPERATORS = [:and, :or, :&, :|] + def filter_logical_operator?(operator) + LOGICAL_OPERATORS.include?(operator) + end + + def normalize_filter_logical_operator(operator) + assert_filter_logical_operator(operator) + case (operator || :and) when :and, :& :and - when :or, :| - :or else - operators = [:and, :or, :&, :|] + :or + end + end + + def assert_filter_logical_operator(operator) + return if operator.nil? + unless filter_logical_operator?(operator) raise ArgumentError, - "invalid logical operator: #{type.inspect}: " + - "available operators: #{operators.inspect}" + "invalid logical operator: #{operator.inspect}: " + + "available operators: #{LOGICAL_OPERATORS.inspect}" end end end Modified: trunk/lib/active_ldap/association/belongs_to.rb ============================================================================== --- trunk/lib/active_ldap/association/belongs_to.rb (original) +++ trunk/lib/active_ldap/association/belongs_to.rb Wed May 9 19:51:17 2007 @@ -30,7 +30,7 @@ def find_target value = @owner[@options[:foreign_key_name]] raise EntryNotFound if value.nil? - filter = "(#{primary_key}=#{value})" + filter = {primary_key => value} result = foreign_class.find(:all, :filter => filter, :limit => 1) raise EntryNotFound if result.empty? result.first Modified: trunk/lib/active_ldap/association/belongs_to_many.rb ============================================================================== --- trunk/lib/active_ldap/association/belongs_to_many.rb (original) +++ trunk/lib/active_ldap/association/belongs_to_many.rb Wed May 9 19:51:17 2007 @@ -30,7 +30,7 @@ key = @options[:many] values = @owner[@options[:foreign_key_name], true].compact components = values.collect do |value| - "(#{key}=#{value})" + [key, value] end foreign_class.find(:all, :filter => [:or, *components]) end Modified: trunk/lib/active_ldap/association/has_many.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many.rb (original) +++ trunk/lib/active_ldap/association/has_many.rb Wed May 9 19:51:17 2007 @@ -21,27 +21,19 @@ [key, val] end.reject do |key, val| key.nil? or val.nil? - end.collect do |key, val| - "(#{key}=#{val})" end foreign_class.find(:all, :filter => [:or, *components]) end def delete_entries(entries) key = primary_key - dn_attribute = foreign_class.dn_attribute components = @owner[@options[:foreign_key_name], true].reject do |value| value.nil? - end.collect do |value| - "(#{key}=#{value})" end - filter = [:and, *components] - entry_components = entries.collect do |entry| - "(#{dn_attribute}=#{entry.id})" - end - entry_filter = [:or, *entry_components] - foreign_class.update_all({primary_key => []}, - [:and, filter, entry_filter]) + filter = [:and, + [:and, {key => components}], + [:or, {foreign_class.dn_attribute => entries.collect(&:id)}]] + foreign_class.update_all({key => []}, filter) end end end Modified: trunk/lib/active_ldap/association/has_many_wrap.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many_wrap.rb (original) +++ trunk/lib/active_ldap/association/has_many_wrap.rb Wed May 9 19:51:17 2007 @@ -27,7 +27,7 @@ foreign_base_key = primary_key requested_targets = @owner[@options[:wrap], true] - component_targets = requested_targets.collect do |value| + components = requested_targets.collect do |value| key = val = nil if foreign_base_key == "dn" key, val = value.split(",")[0].split("=") unless value.empty? @@ -38,11 +38,7 @@ end.reject do |key, val| key.nil? or val.nil? end - return [] if component_targets.empty? - - components = component_targets.collect do |key, val| - "(#{key}=#{val})" - end + return [] if components.empty? klass = foreign_class found_targets = {} Modified: trunk/test/test_adapter.rb ============================================================================== --- trunk/test/test_adapter.rb (original) +++ trunk/test/test_adapter.rb Wed May 9 19:51:17 2007 @@ -59,6 +59,11 @@ [:and, {:objectClass => "*", :uid => [:or, "bob", "alice"]}]) + assert_parse_filter("(&(gidNumber=100001)" + + "(|(uid=temp-user1)(uid=temp-user2)))", + [:and, + [:and, {"gidNumber"=>["100001"]}], + [:or, {"uid"=>["temp-user1", "temp-user2"]}]]) end def test_invalid_operator From codesite-noreply at google.com Thu May 10 01:04:00 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Wed, 09 May 2007 22:04:00 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r179 - trunk/lib/active_ldap/adapter Message-ID: <163600d8e4043016942d75bfa5b1@google.com> Author: koutou Date: Wed May 9 22:03:41 2007 New Revision: 179 Modified: trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/adapter/ldap.rb trunk/lib/active_ldap/adapter/net_ldap.rb Log: * abstracted duplicated codes in Ruby/LDAP and Net::LDAP adapteres. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Wed May 9 22:03:41 2007 @@ -20,7 +20,189 @@ end end + def connect(options={}) + host = options[:host] || @host + port = options[:port] || @port + method = ensure_method(options[:method] || @method) + @connection = yield(host, port, method) + prepare_connection(options) + bind(options) + end + + def disconnect!(options={}) + return if @connection.nil? + unbind(options) + @connection = nil + end + + def rebind(options={}) + unbind(options) if bound? + connect(options) + end + + def bind(options={}) + bind_dn = options[:bind_dn] || @bind_dn + try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl + if options.has_key?(:allow_anonymous) + allow_anonymous = options[:allow_anonymous] + else + allow_anonymous = @allow_anonymous + end + + # Rough bind loop: + # Attempt 1: SASL if available + # Attempt 2: SIMPLE with credentials if password block + # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') + if try_sasl and sasl_bind(bind_dn, options) + @logger.info {'Bound SASL'} + elsif simple_bind(bind_dn, options) + @logger.info {'Bound simple'} + elsif allow_anonymous and bind_as_anonymous(options) + @logger.info {'Bound anonymous'} + else + message = yield if block_given? + message ||= 'All authentication methods exhausted.' + raise AuthenticationError, message + end + + bound? + end + + def bind_as_anonymous(options={}) + @logger.info {"Attempting anonymous authentication"} + operation(options) do + yield + end + end + + def connecting? + not @connection.nil? + end + + def schema(options={}) + @schema ||= operation(options) do + base = options[:base] + attrs = options[:attributes] + + attrs ||= [ + 'objectClasses', + 'attributeTypes', + 'matchingRules', + 'matchingRuleUse', + 'dITStructureRules', + 'dITContentRules', + 'nameForms', + 'ldapSyntaxes', + #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER. + ] + key = 'subschemaSubentry' + base ||= root_dse([key], options)[0][key][0] + base ||= 'cn=schema' + dn, attributes = search(:base => base, + :scope => :base, + :filter => '(objectClass=subschema)', + :attributes => attrs).first + Schema.new(attributes) + end + end + + def load(ldifs, options={}) + operation(options) do + ldifs.split(/(?:\r?\n){2,}/).each do |ldif| + yield(ldif) + end + end + end + + def search(options={}) + filter = parse_filter(options[:filter] || 'objectClass=*') + attrs = options[:attributes] || [] + scope = ensure_scope(options[:scope]) + base = options[:base] + limit = options[:limit] || 0 + limit = nil if limit <= 0 + + attrs = attrs.to_a # just in case + + values = [] + callback = Proc.new do |value, block| + value = block.call(value) if block + values << value + end + + begin + operation(options) do + yield(base, scope, filter, attrs, limit, callback) + end + rescue LdapError + # Do nothing on failure + @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " + + "for #{filter} and attrs #{attrs.inspect}"} + end + + values + end + + def delete(targets, options={}) + targets = [targets] unless targets.is_a?(Array) + return if targets.empty? + target = nil + begin + operation(options) do + targets.each do |target| + yield(target) + end + end + rescue LdapError::NoSuchObject + raise EntryNotFound, "No such entry: #{target}" + end + end + + def add(dn, entries, options={}) + begin + operation(options) do + yield(dn, entries) + end + rescue LdapError::NoSuchObject + raise EntryNotFound, "No such entry: #{dn}" + rescue LdapError::InvalidDnSyntax + raise DistinguishedNameInvalid.new(dn) + rescue LdapError::AlreadyExists + raise EntryAlreadyExist, "#{$!.message}: #{dn}" + rescue LdapError::StrongAuthRequired + raise StrongAuthenticationRequired, "#{$!.message}: #{dn}" + rescue LdapError::ObjectClassViolation + raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + rescue LdapError::UnwillingToPerform + raise OperationNotPermitted, "#{$!.message}: #{dn}" + end + end + + def modify(dn, entries, options={}) + begin + operation(options) do + yield(dn, entries) + end + rescue LdapError::UndefinedType + raise + rescue LdapError::ObjectClassViolation + raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + end + end + private + def prepare_connection(options) + end + + def operation(options) + reconnect_if_need + try_reconnect = !options.has_key?(:try_reconnect) || + options[:try_reconnect] + with_timeout(try_reconnect, options) do + yield + end + end + def need_credential_sasl_mechanism?(mechanism) not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism) end @@ -61,6 +243,52 @@ end end + def sasl_bind(bind_dn, options={}) + return false unless bind_dn + + # Get all SASL mechanisms + mechanisms = operation(options) do + key = "supportedSASLMechanisms" + root_dse([key])[0][key] + end + mechanisms ||= [] + + if options.has_key?(:sasl_quiet) + sasl_quiet = options[:sasl_quiet] + else + sasl_quiet = @sasl_quiet + end + + sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms + sasl_mechanisms.each do |mechanism| + next unless mechanisms.include?(mechanism) + operation(options) do + yield(bind_dn, mechanism, sasl_quiet) + return true if bound? + end + end + false + end + + def simple_bind(bind_dn, options={}) + return false unless bind_dn + + passwd = password(bind_dn, options) + return false unless passwd + + begin + operation(options) do + yield(bind_dn, passwd) + bound? + end + rescue LdapError::InvalidDnSyntax + @logger.debug {"DN is invalid: #{bind_dn}"} + raise DistinguishedNameInvalid.new(bind_dn) + rescue LdapError::InvalidCredentials + false + end + end + def parse_filter(filter, operator=nil) return nil if filter.nil? if !filter.is_a?(String) and !filter.respond_to?(:collect) @@ -175,6 +403,58 @@ "invalid logical operator: #{operator.inspect}: " + "available operators: #{LOGICAL_OPERATORS.inspect}" end + end + + # Attempts to reconnect up to the number of times allowed + # If forced, try once then fail with ConnectionError if not connected. + def reconnect(options={}) + options = options.dup + force = options[:force] + retry_limit = options[:retry_limit] || @retry_limit + retry_wait = options[:retry_wait] || @retry_wait + options[:reconnect_attempts] ||= 0 + + loop do + unless can_reconnect?(options) + raise ConnectionError, + 'Giving up trying to reconnect to LDAP server.' + end + + @logger.debug {'Attempting to reconnect'} + disconnect! + + # Reset the attempts if this was forced. + options[:reconnect_attempts] = 0 if force + options[:reconnect_attempts] += 1 if retry_limit >= 0 + begin + connect(options) + break + rescue => detail + @logger.error {"Reconnect to server failed: #{detail.exception}"} + @logger.error {"Reconnect to server failed backtrace:\n" + + detail.backtrace.join("\n")} + # Do not loop if forced + raise ConnectionError, detail.message if force + end + + # Sleep before looping + sleep retry_wait + end + + true + end + + def reconnect_if_need(options={}) + reconnect(options) if !connecting? and can_reconnect?(options) + end + + # Determine if we have exceed the retry limit or not. + # True is reconnecting is allowed - False if not. + def can_reconnect?(options={}) + retry_limit = options[:retry_limit] || @retry_limit + reconnect_attempts = options[:reconnect_attempts] || 0 + + retry_limit < 0 or reconnect_attempts < (retry_limit - 1) end end end Modified: trunk/lib/active_ldap/adapter/ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap.rb (original) +++ trunk/lib/active_ldap/adapter/ldap.rb Wed May 9 22:03:41 2007 @@ -35,160 +35,57 @@ end def connect(options={}) - method = ensure_method(options[:method] || @method) - host = options[:host] || @host - port = options[:port] || @port - - @connection = method.connect(host, port) - operation(options) do - @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) - end - bind(options) - end - - def schema(options={}) - @schema ||= operation(options) do - base = options[:base] - attrs = options[:attributes] - sec = options[:sec] || 0 - usec = options[:usec] || 0 - - attrs ||= [ - 'objectClasses', - 'attributeTypes', - 'matchingRules', - 'matchingRuleUse', - 'dITStructureRules', - 'dITContentRules', - 'nameForms', - 'ldapSyntaxes', - #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER. - ] - key = 'subschemaSubentry' - base ||= @connection.root_dse([key], sec, usec)[0][key][0] - base ||= 'cn=schema' - result = @connection.search2(base, ensure_scope(:base), - '(objectClass=subschema)', attrs, false, - sec, usec).first - Schema.new(result) - end -# rescue -# raise ConnectionError.new("Unable to retrieve schema from " + -# "server (#{@method.class.downcase})") - end - - def disconnect!(options={}) - return if @connection.nil? - begin - unbind(options) - #rescue + super do |host, port, method| + method.connect(host, port) end - @connection = nil - # Make sure it is cleaned up - # This causes Ruby/LDAP memory corruption. - # GC.start end def unbind(options={}) return unless bound? operation(options) do - @connection.unbind + execute(:unbind) end end - def rebind(options={}) - unbind(options) if bound? - connect(options) - end - def bind(options={}) - bind_dn = options[:bind_dn] || @bind_dn - try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl - if options.has_key?(:allow_anonymous) - allow_anonymous = options[:allow_anonymous] - else - allow_anonymous = @allow_anonymous + super do + @connection.error_message end - - # Rough bind loop: - # Attempt 1: SASL if available - # Attempt 2: SIMPLE with credentials if password block - # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') - if try_sasl and sasl_bind(bind_dn, options) - @logger.info {'Bound SASL'} - elsif simple_bind(bind_dn, options) - @logger.info {'Bound simple'} - elsif allow_anonymous and bind_as_anonymous(options) - @logger.info {'Bound anonymous'} - else - message = @connection.error_message - message ||= 'All authentication methods exhausted.' - raise AuthenticationError, message - end - - bound? end def bind_as_anonymous(options={}) - @logger.info {"Attempting anonymous authentication"} - operation(options) do - @connection.bind + super do + execute(:bind) true end end - def connecting? - not @connection.nil? - end - def bound? connecting? and @connection.bound? end - # search - # - # Wraps Ruby/LDAP connection.search to make it easier to search for - # specific data without cracking open Base.connection - def search(options={}) - filter = parse_filter(options[:filter] || 'objectClass=*') - attrs = options[:attributes] || [] - scope = ensure_scope(options[:scope]) - base = options[:base] - limit = options[:limit] || 0 - limit = nil if limit <= 0 - - values = [] - attrs = attrs.to_a # just in case - - begin - operation(options) do + def search(options={}, &block) + super(options) do |base, scope, filter, attrs, limit, callback| + begin i = 0 - @connection.search(base, scope, filter, attrs) do |m| + execute(:search, base, scope, filter, attrs) do |entry| i += 1 attributes = {} - m.attrs.each do |attr| - attributes[attr] = m.vals(attr) + entry.attrs.each do |attr| + attributes[attr] = entry.vals(attr) end - value = [m.dn, attributes] - value = yield(value) if block_given? - values.push(value) + callback.call([entry.dn, attributes], block) break if limit and limit >= i end - end - rescue LdapError - # Do nothing on failure - @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " + - "for #{filter} and attrs #{attrs.inspect}"} - rescue RuntimeError - if $!.message == "no result returned by search" - @logger.debug {"No matches for #{filter} and attrs " + - "#{attrs.inspect}"} - else - raise + rescue RuntimeError + if $!.message == "no result returned by search" + @logger.debug {"No matches for #{filter} and attrs " + + "#{attrs.inspect}"} + else + raise + end end end - - values end def to_ldif(dn, attributes) @@ -202,72 +99,48 @@ end def load(ldifs, options={}) - operation(options) do - ldifs.split(/(?:\r?\n){2,}/).each do |ldif| - LDAP::LDIF.parse_entry(ldif).send(@connection) - end + super do |ldif| + LDAP::LDIF.parse_entry(ldif).send(@connection) end end def delete(targets, options={}) - targets = [targets] unless targets.is_a?(Array) - return if targets.empty? - target = nil - begin - operation(options) do - targets.each do |target| - @connection.delete(target) - end - end - rescue LdapError::NoSuchObject - raise EntryNotFound, "No such entry: #{target}" + super do |target| + execute(:delete, target) end end def add(dn, entries, options={}) - begin - operation(options) do - @connection.add(dn, parse_entries(entries)) - end - rescue LdapError::NoSuchObject - raise EntryNotFound, "No such entry: #{dn}" - rescue LdapError::InvalidDnSyntax - raise DistinguishedNameInvalid.new(dn) - rescue LdapError::AlreadyExists - raise EntryAlreadyExist, "#{$!.message}: #{dn}" - rescue LdapError::StrongAuthRequired - raise StrongAuthenticationRequired, "#{$!.message}: #{dn}" - rescue LdapError::ObjectClassViolation - raise RequiredAttributeMissed, "#{$!.message}: #{dn}" - rescue LdapError::UnwillingToPerform - raise OperationNotPermitted, "#{$!.message}: #{dn}" + super do |dn, entries| + execute(:add, dn, parse_entries(entries)) end end def modify(dn, entries, options={}) - begin - operation(options) do - @connection.modify(dn, parse_entries(entries)) - end - rescue LdapError::UndefinedType - raise - rescue LdapError::ObjectClassViolation - raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + super do |dn, entries| + execute(:modify, dn, parse_entries(entries)) end end private - def operation(options={}, &block) - reconnect_if_need - try_reconnect = !options.has_key?(:try_reconnect) || - options[:try_reconnect] - with_timeout(try_reconnect, options) do - begin - block.call - rescue LDAP::ResultError - @connection.assert_error_code - raise $!.message - end + def prepare_connection(options={}) + operation(options) do + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + end + end + + def root_dse(attributes, options={}) + sec = options[:sec] || 0 + usec = options[:usec] || 0 + @connection.root_dse(attributes, sec, usec) + end + + def execute(method, *args, &block) + begin + @connection.send(method, *args, &block) + rescue LDAP::ResultError + @connection.assert_error_code + raise $!.message end end @@ -311,55 +184,25 @@ value end - # Bind to LDAP with the given DN using any available SASL methods def sasl_bind(bind_dn, options={}) - return false unless bind_dn - - # Get all SASL mechanisms - mechanisms = operation do - @connection.root_dse[0]['supportedSASLMechanisms'] - end - mechanisms ||= [] - - if options.has_key?(:sasl_quiet) - sasl_quiet = options[:sasl_quiet] - else - sasl_quiet = @sasl_quiet - end - - sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms - sasl_mechanisms.each do |mechanism| - next unless mechanisms.include?(mechanism) - operation do - @connection.sasl_quiet = sasl_quiet unless sasl_quiet.nil? + super do |bind_dn, mechanism, quiet| + begin + sasl_quiet = @connection.sasl_quiet + @connection.sasl_quiet = quiet unless quiet.nil? args = [bind_dn, mechanism] if need_credential_sasl_mechanism?(mechanism) args << password(bind_dn, options) end - @connection.sasl_bind(*args) - return true if @connection.bound? + execute(:sasl_bind, *args) + ensure + @connection.sasl_quiet = sasl_quiet end end - false end - # Bind to LDAP with the given DN and password def simple_bind(bind_dn, options={}) - return false unless bind_dn - - passwd = password(bind_dn, options) - return false unless passwd - - begin - operation do - @connection.bind(bind_dn, passwd) - true - end - rescue LdapError::InvalidDnSyntax - @logger.debug {"DN is invalid: #{bind_dn}"} - raise DistinguishedNameInvalid.new(bind_dn) - rescue LdapError::InvalidCredentials - false + super do |bind_dn, passwd| + execute(:bind, bind_dn, passwd) end end @@ -383,58 +226,6 @@ else raise ArgumentError, "unknown type: #{type}" end - end - - # Attempts to reconnect up to the number of times allowed - # If forced, try once then fail with ConnectionError if not connected. - def reconnect(options={}) - options = options.dup - force = options[:force] - retry_limit = options[:retry_limit] || @retry_limit - retry_wait = options[:retry_wait] || @retry_wait - options[:reconnect_attempts] ||= 0 - - loop do - unless can_reconnect?(options) - raise ConnectionError, - 'Giving up trying to reconnect to LDAP server.' - end - - @logger.debug {'Attempting to reconnect'} - disconnect! - - # Reset the attempts if this was forced. - options[:reconnect_attempts] = 0 if force - options[:reconnect_attempts] += 1 if retry_limit >= 0 - begin - connect(options) - break - rescue => detail - @logger.error {"Reconnect to server failed: #{detail.exception}"} - @logger.error {"Reconnect to server failed backtrace:\n" + - detail.backtrace.join("\n")} - # Do not loop if forced - raise ConnectionError, detail.message if force - end - - # Sleep before looping - sleep retry_wait - end - - true - end - - def reconnect_if_need(options={}) - reconnect(options) if !connecting? and can_reconnect?(options) - end - - # Determine if we have exceed the retry limit or not. - # True is reconnecting is allowed - False if not. - def can_reconnect?(options={}) - retry_limit = options[:retry_limit] || @retry_limit - reconnect_attempts = options[:reconnect_attempts] || 0 - - retry_limit < 0 or reconnect_attempts < (retry_limit - 1) end end end Modified: trunk/lib/active_ldap/adapter/net_ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/net_ldap.rb (original) +++ trunk/lib/active_ldap/adapter/net_ldap.rb Wed May 9 22:03:41 2007 @@ -22,151 +22,58 @@ def connect(options={}) @bound = false - - method = ensure_method(options[:method] || @method) - host = options[:host] || @host - port = options[:port] || @port - - config = { - :host => host, - :port => port, - :encryption => {:method => method}, - } - @connection = Net::LDAP::Connection.new(config) - bind(options) - end - - def schema(options={}) - @schema ||= operation(options) do - base = options[:base] - attrs = options[:attributes] - - key = 'subschemaSubentry' - attrs ||= [ - 'objectClasses', - 'attributeTypes', - 'matchingRules', - 'matchingRuleUse', - 'dITStructureRules', - 'dITContentRules', - 'nameForms', - 'ldapSyntaxes', - #'extendedAttributeInfo', # if we need RANGE-LOWER/UPPER. - ] - base ||= root_dse([key])[key][0] - base ||= 'cn=schema' - result = search(:base => base, - :scope => :base, - :filter => '(objectClass=subschema)', - :attributes => attrs)[0][1] - Schema.new(result) - end -# rescue -# raise ConnectionError.new("Unable to retrieve schema from " + -# "server (#{@method.class.downcase})") - end - - def disconnect!(options={}) - return if @connection.nil? - unbind(options) - @connection = nil + super do |host, port, method| + config = { + :host => host, + :port => port, + :encryption => {:method => method}, + } + Net::LDAP::Connection.new(config) + end end def unbind(options={}) @bound = false end - def rebind(options={}) - unbind(options) if bound? - connect(options) - end - def bind(options={}) - bind_dn = options[:bind_dn] || @bind_dn - try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl - if options.has_key?(:allow_anonymous) - allow_anonymous = options[:allow_anonymous] - else - allow_anonymous = @allow_anonymous - end - + @bound = false begin - # Rough bind loop: - # Attempt 1: SASL if available - # Attempt 2: SIMPLE with credentials if password block - # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') - if try_sasl and sasl_bind(bind_dn, options) - @bound = true - @logger.info {'Bound SASL'} - elsif simple_bind(bind_dn, options) - @bound = true - @logger.info {'Bound simple'} - elsif allow_anonymous and bind_as_anonymous(options) - @bound = true - @logger.info {'Bound anonymous'} - else - @bound = false - raise AuthenticationError, 'All authentication methods exhausted.' - end + super rescue Net::LDAP::LdapError raise AuthenticationError, $!.message end - - bound? end def bind_as_anonymous(options={}) - @logger.info {"Attempting anonymous authentication"} - operation(options) do - (@connection.bind :method => :anonymous).zero? + super do + @bound = false + execute(:bind, :method => :anonymous) + @bound = true end end - def connecting? - not @connection.nil? - end - def bound? connecting? and @bound end - def search(options={}) - filter = parse_filter(options[:filter] || 'objectClass=*') - attrs = options[:attributes] || [] - scope = ensure_scope(options[:scope]) - base = options[:base] - limit = options[:limit] || 0 - limit = nil if limit <= 0 - - results = [] - attrs = attrs.to_a # just in case - - begin - operation(options) do - args = { - :base => base, - :scope => scope, - :filter => filter, - :attributes => attrs, - :size => limit, - } - execute(:search, args) do |entry| - attributes = {} - entry.original_attribute_names.each do |name| - attributes[name] = entry[name] - end - value = [entry.dn, attributes] - value = yield(value) if block_given? - results.push(value) + def search(options={}, &block) + super(options) do |base, scope, filter, attrs, limit, callback| + args = { + :base => base, + :scope => scope, + :filter => filter, + :attributes => attrs, + :size => limit, + } + execute(:search, args) do |entry| + attributes = {} + entry.original_attribute_names.each do |name| + attributes[name] = entry[name] end + callback.call([entry.dn, attributes], block) end - rescue LdapError - # Do nothing on failure - @logger.debug {"Ignore error #{$!.class}(#{$!.message}) " + - "for #{filter} and attrs #{attrs.inspect}"} end - - results end def to_ldif(dn, attributes) @@ -178,88 +85,46 @@ end def load(ldifs, options={}) - operation(options) do - ldifs.split(/(?:\r?\n){2,}/).each do |ldif| - entry = Net::LDAP::Entry.from_single_ldif_string(ldif) - attributes = {} - entry.each do |name, values| - attributes[name] = values - end - attributes.delete(:dn) - execute(:add, - :dn => entry.dn, - :attributes => attributes) - end + super do |ldif| + entry = Net::LDAP::Entry.from_single_ldif_string(ldif) + attributes = {} + entry.each do |name, values| + attributes[name] = values + end + attributes.delete(:dn) + execute(:add, + :dn => entry.dn, + :attributes => attributes) end end def delete(targets, options={}) - targets = [targets] unless targets.is_a?(Array) - return if targets.empty? - target = nil - begin - operation(options) do - targets.each do |target| - execute(:delete, :dn => target) - end - end - rescue LdapError::NoSuchObject - raise EntryNotFound, "No such entry: #{target}" + super do |target| + execute(:delete, :dn => target) end end def add(dn, entries, options={}) - begin - operation(options) do - attributes = {} - entries.each do |type, key, attrs| - attrs.each do |name, values| - attributes[name] = values - end + super do |dn, entries| + attributes = {} + entries.each do |type, key, attrs| + attrs.each do |name, values| + attributes[name] = values end - execute(:add, - :dn => dn, - :attributes => attributes) end - rescue LdapError::NoSuchObject - raise EntryNotFound, "No such entry: #{dn}" - rescue LdapError::InvalidDnSyntax - raise DistinguishedNameInvalid.new(dn) - rescue LdapError::AlreadyExists - raise EntryAlreadyExist, "#{$!.message}: #{dn}" - rescue LdapError::StrongAuthRequired - raise StrongAuthenticationRequired, "#{$!.message}: #{dn}" - rescue LdapError::ObjectClassViolation - raise RequiredAttributeMissed, "#{$!.message}: #{dn}" - rescue LdapError::UnwillingToPerform - raise OperationNotPermitted, "#{$!.message}: #{dn}" + execute(:add, :dn => dn, :attributes => attributes) end end def modify(dn, entries, options={}) - begin - operation(options) do - execute(:modify, - :dn => dn, - :operations => parse_entries(entries)) - end - rescue LdapError::UndefinedType - raise - rescue LdapError::ObjectClassViolation - raise RequiredAttributeMissed, "#{$!.message}: #{dn}" + super do |dn, entries| + execute(:modify, + :dn => dn, + :operations => parse_entries(entries)) end end private - def operation(options={}, &block) - reconnect_if_need - try_reconnect = !options.has_key?(:try_reconnect) || - options[:try_reconnect] - with_timeout(try_reconnect, options) do - block.call - end - end - def execute(method, *args, &block) result = @connection.send(method, *args, &block) message = nil @@ -275,12 +140,12 @@ end end - def root_dse(attrs) - entry = search(:base => "", - :scope => :base, - :attributes => attrs).first - dn, attributes = entry - attributes + def root_dse(attrs, options={}) + search(:base => "", + :scope => :base, + :attributes => attrs).collect do |dn, attributes| + attributes + end end def ensure_method(method) @@ -309,57 +174,34 @@ value end - # Bind to LDAP with the given DN using any available SASL methods def sasl_bind(bind_dn, options={}) - return false unless bind_dn - - # Get all SASL mechanisms - mechanisms = operation(options) do - key = "supportedSASLMechanisms" - root_dse([key])[key] - end - mechanisms ||= [] - - sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms - sasl_mechanisms.each do |mechanism| - next unless mechanisms.include?(mechanism) - operation(options) do - args = { - :method => :sasl, - :initial_credential => bind_dn, - :mechanism => mechanism, - } - if need_credential_sasl_mechanism?(mechanism) - args[:challenge_response] = Proc.new do |cred| - password(cred, options) - end + super do |bind_dn, mechanism, quiet| + args = { + :method => :sasl, + :initial_credential => bind_dn, + :mechanism => mechanism, + } + if need_credential_sasl_mechanism?(mechanism) + args[:challenge_response] = Proc.new do |cred| + password(cred, options) end - @connection.bind(args) - return true if @connection.bound? end + @bound = false + execute(:bind, args) + @bound = true end - false end - # Bind to LDAP with the given DN and password def simple_bind(bind_dn, options={}) - return false unless bind_dn - - passwd = password(bind_dn, options) - return false unless passwd - - begin - operation do - args = { - :method => :simple, - :username => bind_dn, - :password => passwd, - } - @connection.bind(args).zero? - end - rescue Net::LDAP::LdapError - @logger.debug {"Failed to bind as DN: #{bind_dn}"} - false + super do |bind_dn, passwd| + args = { + :method => :simple, + :username => bind_dn, + :password => passwd, + } + @bound = false + execute(:bind, args) + @bound = true end end @@ -381,58 +223,6 @@ else raise ArgumentError, "unknown type: #{type}" end - end - - # Attempts to reconnect up to the number of times allowed - # If forced, try once then fail with ConnectionError if not connected. - def reconnect(options={}) - options = options.dup - force = options[:force] - retry_limit = options[:retry_limit] || @retry_limit - retry_wait = options[:retry_wait] || @retry_wait - options[:reconnect_attempts] ||= 0 - - loop do - unless can_reconnect?(options) - raise ConnectionError, - 'Giving up trying to reconnect to LDAP server.' - end - - @logger.debug {'Attempting to reconnect'} - disconnect! - - # Reset the attempts if this was forced. - options[:reconnect_attempts] = 0 if force - options[:reconnect_attempts] += 1 if retry_limit >= 0 - begin - connect(options) - break - rescue => detail - @logger.error {"Reconnect to server failed: #{detail.exception}"} - @logger.error {"Reconnect to server failed backtrace:\n" + - detail.backtrace.join("\n")} - # Do not loop if forced - raise ConnectionError, detail.message if force - end - - # Sleep before looping - sleep retry_wait - end - - true - end - - def reconnect_if_need(options={}) - reconnect(options) if !connecting? and can_reconnect?(options) - end - - # Determine if we have exceed the retry limit or not. - # True is reconnecting is allowed - False if not. - def can_reconnect?(options={}) - retry_limit = options[:retry_limit] || @retry_limit - reconnect_attempts = options[:reconnect_attempts] || 0 - - retry_limit < 0 or reconnect_attempts < (retry_limit - 1) end end end From codesite-noreply at google.com Thu May 10 01:07:50 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Wed, 09 May 2007 22:07:50 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r180 - trunk Message-ID: <163600d1b5043016a1e5f2a9a306@google.com> Author: koutou Date: Wed May 9 22:07:35 2007 New Revision: 180 Modified: trunk/TODO Log: * TODO: removed some tasks: - Fix case sensitivity in object classes - support Net::LDAP as LDAP backend after Net::LDAP supports START_TLS. (I made a patch and submitted to the bug tracker of Net::LDAP) Modified: trunk/TODO ============================================================================== --- trunk/TODO (original) +++ trunk/TODO Wed May 9 22:07:35 2007 @@ -1,5 +1,4 @@ - Test SASL bind. -- Fix case sensitivity in object classes - Add result pagination via LDAP::Controls - serialize & serialized_attributes - schema mgmt - how does AR handle it? @@ -10,9 +9,6 @@ ActiveLdap exception. I think we need to develop an application using ActiveLdap. - support Ruby/GetText. -- support Net::LDAP as LDAP backend after Net::LDAP - supports START_TLS. (I made a patch and submitted to the - bug tracker of Net::LDAP) - Add locking around Timeout.alarm() to ensure a multithreaded ruby app doesn't hit any race conditions - Add AR matching exceptions: @@ -25,5 +21,3 @@ * MultiparameterAssignmentErrors * AttributeAssignmentError * RecordNotSaved - - From codesite-noreply at google.com Thu May 10 04:29:24 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 10 May 2007 01:29:24 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r181 - in trunk: lib/active_ldap lib/active_ldap/adapter test Message-ID: <163600d40604301972c79fdf131df@google.com> Author: koutou Date: Thu May 10 01:29:05 2007 New Revision: 181 Modified: trunk/lib/active_ldap/adapter/net_ldap.rb trunk/lib/active_ldap/configuration.rb trunk/test/config.yaml.sample Log: * supported SASL Digest-MD5 authentication by Net::LDAP. Modified: trunk/lib/active_ldap/adapter/net_ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/net_ldap.rb (original) +++ trunk/lib/active_ldap/adapter/net_ldap.rb Thu May 10 01:29:05 2007 @@ -1,3 +1,5 @@ +require 'digest/md5' + require 'active_ldap/adapter/base' module ActiveLdap @@ -176,20 +178,78 @@ def sasl_bind(bind_dn, options={}) super do |bind_dn, mechanism, quiet| + normalized_mechanism = mechanism.downcase.gsub(/-/, '_') + sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}" + next unless respond_to?(sasl_bind_setup, true) + initial_credential, challenge_response = + send(sasl_bind_setup, bind_dn, options) args = { :method => :sasl, - :initial_credential => bind_dn, + :initial_credential => initial_credential, :mechanism => mechanism, + :challenge_response => challenge_response, } - if need_credential_sasl_mechanism?(mechanism) - args[:challenge_response] = Proc.new do |cred| - password(cred, options) - end - end @bound = false execute(:bind, args) @bound = true end + end + + def sasl_bind_setup_digest_md5(bind_dn, options) + initial_credential = "" + nonce_count = 1 + challenge_response = Proc.new do |cred| + params = parse_sasl_digest_md5_credential(cred) + qops = params["qop"].split(/,/) + return "unsupported qops: #{qops.inspect}" unless qops.include?("auth") + qop = "auth" + server = @connection.instance_variable_get("@conn").addr[2] + realm = params['realm'] + uri = "ldap/#{server}" + nc = "%08x" % nonce_count + nonce = params["nonce"] + cnonce = generate_client_nonce + requests = { + :username => bind_dn.inspect, + :realm => realm.inspect, + :nonce => nonce.inspect, + :cnonce => cnonce.inspect, + :nc => nc, + :qop => qop, + :maxbuf => "65536", + "digest-uri" => uri.inspect, + } + a1 = "#{bind_dn}:#{realm}:#{password(cred, options)}" + a1 = "#{Digest::MD5.digest(a1)}:#{nonce}:#{cnonce}" + ha1 = Digest::MD5.hexdigest(a1) + a2 = "AUTHENTICATE:#{uri}" + ha2 = Digest::MD5.hexdigest(a2) + response = "#{ha1}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{ha2}" + requests["response"] = Digest::MD5.hexdigest(response) + nonce_count += 1 + requests.collect do |key, value| + "#{key}=#{value}" + end.join(",") + end + [initial_credential, challenge_response] + end + + def parse_sasl_digest_md5_credential(cred) + params = {} + cred.scan(/(\w+)=(\"?)(.+?)\2(?:,|$)/) do |name, sep, value| + params[name] = value + end + params + end + + CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + def generate_client_nonce(size=32) + return "RMrXWLEpnxIn+QrYjhocwbvHfo8Dx3Se6aFxmPwTF00=" + nonce = "" + size.times do |i| + nonce << CHARS[rand(CHARS.size)] + end + nonce end def simple_bind(bind_dn, options={}) Modified: trunk/lib/active_ldap/configuration.rb ============================================================================== --- trunk/lib/active_ldap/configuration.rb (original) +++ trunk/lib/active_ldap/configuration.rb Thu May 10 01:29:05 2007 @@ -23,7 +23,8 @@ DEFAULT_CONFIG[:sasl_quiet] = false DEFAULT_CONFIG[:try_sasl] = false # See http://www.iana.org/assignments/sasl-mechanisms - DEFAULT_CONFIG[:sasl_mechanisms] = ["GSSAPI", "CRAM-MD5", "EXTERNAL"] + DEFAULT_CONFIG[:sasl_mechanisms] = ["GSSAPI", "DIGEST-MD5", + "CRAM-MD5", "EXTERNAL"] DEFAULT_CONFIG[:retry_limit] = 3 DEFAULT_CONFIG[:retry_wait] = 3 Modified: trunk/test/config.yaml.sample ============================================================================== --- trunk/test/config.yaml.sample (original) +++ trunk/test/config.yaml.sample Thu May 10 01:29:05 2007 @@ -4,4 +4,3 @@ method: :tls bind_dn: cn=user-name,dc=localdomain password: secret -# adapter: net-ldap From codesite-noreply at google.com Thu May 10 11:23:39 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 10 May 2007 08:23:39 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r182 - trunk/lib/active_ldap/adapter Message-ID: Author: koutou Date: Thu May 10 08:23:23 2007 New Revision: 182 Modified: trunk/lib/active_ldap/adapter/net_ldap.rb Log: * fixed non-encrypt connection. Modified: trunk/lib/active_ldap/adapter/net_ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/net_ldap.rb (original) +++ trunk/lib/active_ldap/adapter/net_ldap.rb Thu May 10 08:23:23 2007 @@ -28,8 +28,8 @@ config = { :host => host, :port => port, - :encryption => {:method => method}, } + config[:encryption] = {:method => method} if method Net::LDAP::Connection.new(config) end end From codesite-noreply at google.com Thu May 10 11:29:54 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 10 May 2007 08:29:54 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r183 - trunk/lib/active_ldap Message-ID: <163600d40604301f529a553d2066b@google.com> Author: koutou Date: Thu May 10 08:29:37 2007 New Revision: 183 Modified: trunk/lib/active_ldap/base.rb Log: * removed needless method. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Thu May 10 08:29:37 2007 @@ -1069,10 +1069,6 @@ end private - def logger - @@logger - end - def extract_object_class(attributes) classes = [] attrs = attributes.reject do |key, value| From codesite-noreply at google.com Thu May 10 11:44:13 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 10 May 2007 08:44:13 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r184 - in trunk/lib/active_ldap: . adapter Message-ID: <163600d68504301f85c3081420b93@google.com> Author: koutou Date: Thu May 10 08:43:54 2007 New Revision: 184 Modified: trunk/lib/active_ldap/adapter/ldap_ext.rb trunk/lib/active_ldap/adapter/net_ldap.rb trunk/lib/active_ldap/base.rb trunk/lib/active_ldap/validations.rb Log: * plugged warnings. Modified: trunk/lib/active_ldap/adapter/ldap_ext.rb ============================================================================== --- trunk/lib/active_ldap/adapter/ldap_ext.rb (original) +++ trunk/lib/active_ldap/adapter/ldap_ext.rb Thu May 10 08:43:54 2007 @@ -5,6 +5,7 @@ module LDAP class Mod unless instance_method(:to_s).arity.zero? + alias_method :original_to_s, :to_s def to_s inspect end Modified: trunk/lib/active_ldap/adapter/net_ldap.rb ============================================================================== --- trunk/lib/active_ldap/adapter/net_ldap.rb (original) +++ trunk/lib/active_ldap/adapter/net_ldap.rb Thu May 10 08:43:54 2007 @@ -244,7 +244,6 @@ CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a def generate_client_nonce(size=32) - return "RMrXWLEpnxIn+QrYjhocwbvHfo8Dx3Se6aFxmPwTF00=" nonce = "" size.times do |i| nonce << CHARS[rand(CHARS.size)] Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Thu May 10 08:43:54 2007 @@ -458,7 +458,7 @@ if prefix.match(_dn) begin dn_suffix ||= DN.parse(base) - DN.parse(_dn) - dn_suffix + dn_prefix = DN.parse(_dn) - dn_suffix true rescue DistinguishedNameInvalid, ArgumentError false Modified: trunk/lib/active_ldap/validations.rb ============================================================================== --- trunk/lib/active_ldap/validations.rb (original) +++ trunk/lib/active_ldap/validations.rb Thu May 10 08:43:54 2007 @@ -12,23 +12,20 @@ validate :validate_required_values class << self - alias_method :evaluate_condition_for_active_record, - :evaluate_condition - def evaluate_condition(condition, entry) - evaluate_condition_for_active_record(condition, entry) + def evaluate_condition_with_active_ldap_support(condition, entry) + evaluate_condition_without_active_ldap_support(condition, entry) rescue ActiveRecord::ActiveRecordError raise Error, $!.message end + alias_method_chain :evaluate_condition, :active_ldap_support end - alias_method :save_with_validation_for_active_record!, - :save_with_validation! - def save_with_validation! - save_with_validation_for_active_record! + def save_with_active_ldap_support! + save_without_active_ldap_support! rescue ActiveRecord::RecordInvalid raise EntryInvalid, $!.message end - alias_method :save!, :save_with_validation! + alias_method_chain :save!, :active_ldap_support def valid? ensure_apply_object_class @@ -67,12 +64,12 @@ end private - alias_method :run_validations_for_active_record, :run_validations - def run_validations(validation_method) - run_validations_for_active_record(validation_method) + def run_validations_with_active_ldap_support(validation_method) + run_validations_without_active_ldap_support(validation_method) rescue ActiveRecord::ActiveRecordError raise Error, $!.message end + alias_method_chain :run_validations, :active_ldap_support end end end From codesite-noreply at google.com Thu May 10 11:50:11 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 10 May 2007 08:50:11 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r185 - trunk Message-ID: Author: koutou Date: Thu May 10 08:49:54 2007 New Revision: 185 Modified: trunk/Manifest.txt trunk/Rakefile Log: * updated Manifest.txt. Modified: trunk/Manifest.txt ============================================================================== --- trunk/Manifest.txt (original) +++ trunk/Manifest.txt Thu May 10 08:49:54 2007 @@ -28,8 +28,11 @@ examples/usermod-binary-del examples/usermod-lang-add lib/active_ldap.rb -lib/active_ldap/adaptor/base.rb -lib/active_ldap/adaptor/ldap.rb +lib/active_ldap/adapter/base.rb +lib/active_ldap/adapter/ldap.rb +lib/active_ldap/adapter/ldap_ext.rb +lib/active_ldap/adapter/net_ldap.rb +lib/active_ldap/adapter/net_ldap_ext.rb lib/active_ldap/association/belongs_to.rb lib/active_ldap/association/belongs_to_many.rb lib/active_ldap/association/collection.rb @@ -44,7 +47,7 @@ lib/active_ldap/configuration.rb lib/active_ldap/connection.rb lib/active_ldap/distinguished_name.rb -lib/active_ldap/ldap.rb +lib/active_ldap/ldap_error.rb lib/active_ldap/object_class.rb lib/active_ldap/schema.rb lib/active_ldap/timeout.rb @@ -52,6 +55,8 @@ lib/active_ldap/user_password.rb lib/active_ldap/validations.rb rails/plugin/active_ldap/README +rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml rails/plugin/active_ldap/init.rb test/TODO test/al-test-utils.rb @@ -61,6 +66,7 @@ test/test-unit-ext.rb test/test-unit-ext/always-show-result.rb test/test-unit-ext/priority.rb +test/test_adapter.rb test/test_associations.rb test/test_attributes.rb test/test_base.rb Modified: trunk/Rakefile ============================================================================== --- trunk/Rakefile (original) +++ trunk/Rakefile Thu May 10 08:49:54 2007 @@ -11,9 +11,9 @@ project.email = ['will at alum.bu.edu', 'kou at cozmixng.org'] project.summary = 'Ruby/ActiveLdap is a object-oriented API to LDAP' project.url = 'http://rubyforge.org/projects/ruby-activeldap/' - project.test_globs = ['test/**'] + project.test_globs = ['test/test_*.rb'] project.changes = project.paragraphs_of('CHANGES', 0..1).join("\n\n") - project.extra_deps = [['log4r','>= 1.0.4'], 'activerecord'] + project.extra_deps = [['log4r', '>= 1.0.4'], 'activerecord'] project.spec_extras = { :requirements => ['ruby-ldap >= 0.8.2', '(Open)LDAP server'], :autorequire => 'active_ldap' From codesite-noreply at google.com Sat May 12 10:09:17 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 12 May 2007 07:09:17 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r186 - in trunk: lib/active_ldap test Message-ID: Author: koutou Date: Sat May 12 07:08:59 2007 New Revision: 186 Modified: trunk/lib/active_ldap/schema.rb trunk/test/test_object_class.rb trunk/test/test_schema.rb Log: * supported empty schema. Modified: trunk/lib/active_ldap/schema.rb ============================================================================== --- trunk/lib/active_ldap/schema.rb (original) +++ trunk/lib/active_ldap/schema.rb Sat May 12 07:08:59 2007 @@ -1,7 +1,7 @@ module ActiveLdap class Schema def initialize(entries) - @entries = entries + @entries = default_entries.merge(entries || {}) @schema_info = {} @class_attributes_info = {} @cache = {} @@ -48,7 +48,8 @@ # Check already parsed options first return ids[id] if ids.has_key?(id) - while schema = @entries[group].shift + schemata = @entries[group] || [] + while schema = schemata.shift next unless /\A\s*\(\s*(#{OID_RE})\s*(.*)\s*\)\s*\z/ =~ schema schema_id = $1 rest = $2 @@ -285,6 +286,14 @@ def normalize_attribute_name(name) name.upcase.gsub(/_/, "-") + end + + def default_entries + { + "objectClasses" => [], + "attributeTypes" => [], + "ldapSyntaxes" => [], + } end end # Schema end Modified: trunk/test/test_object_class.rb ============================================================================== --- trunk/test/test_object_class.rb (original) +++ trunk/test/test_object_class.rb Sat May 12 07:08:59 2007 @@ -42,6 +42,4 @@ assert_raises(TypeError) {group.add_class(:posixAccount)} end end - - priority :normal end Modified: trunk/test/test_schema.rb ============================================================================== --- trunk/test/test_schema.rb (original) +++ trunk/test/test_schema.rb Sat May 12 07:08:59 2007 @@ -2,6 +2,18 @@ class TestSchema < Test::Unit::TestCase priority :must + def test_empty_schema + assert_make_schema_with_empty_entries(nil) + assert_make_schema_with_empty_entries({}) + assert_make_schema_with_empty_entries({"someValues" => ["aValue"]}) + end + + def test_empty_schema_value + schema = ActiveLdap::Schema.new({"attributeTypes" => nil}) + assert_equal([], schema["attributeTypes", "cn", "DESC"]) + end + + priority :normal def test_attribute_name_with_under_score top_schema = "( 2.5.6.0 NAME 'Top' STRUCTURAL MUST objectClass MAY ( " + @@ -59,7 +71,6 @@ organizational_person_schema) end - priority :normal def test_text_oid text_oid_schema = "( mysite-oid NAME 'mysite' " + "SUP groupofuniquenames STRUCTURAL " + @@ -249,5 +260,14 @@ actual[normalized_key] = schema[sub, name, normalized_key] end assert_equal(normalized_expect, actual) + end + + def assert_make_schema_with_empty_entries(entries) + schema = ActiveLdap::Schema.new(entries) + assert_equal([], schema["attributeTypes", "cn", "DESC"]) + assert_equal([], schema["ldapSyntaxes", + "1.3.6.1.4.1.1466.115.121.1.5", + "DESC"]) + assert_equal([], schema["objectClasses", "posixAccount", "MUST"]) end end From codesite-noreply at google.com Mon May 14 20:39:16 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Mon, 14 May 2007 17:39:16 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r187 - trunk Message-ID: <163600d8e404307776a6432e12d0b6@google.com> Author: koutou Date: Mon May 14 17:38:57 2007 New Revision: 187 Modified: trunk/Rakefile Log: * added workaround for foolish Hoe's executables guess. Modified: trunk/Rakefile ============================================================================== --- trunk/Rakefile (original) +++ trunk/Rakefile Mon May 14 17:38:57 2007 @@ -16,7 +16,8 @@ project.extra_deps = [['log4r', '>= 1.0.4'], 'activerecord'] project.spec_extras = { :requirements => ['ruby-ldap >= 0.8.2', '(Open)LDAP server'], - :autorequire => 'active_ldap' + :autorequire => 'active_ldap', + :executables => [], } project.description = String.new(<<-EOF) 'Ruby/ActiveLdap' is a ruby extension library which provides a clean @@ -66,4 +67,4 @@ system "svn copy -m 'New release tag' https://ruby-activeldap.googlecode.com/svn/trunk https://ruby-activeldap.googlecode.com/svn/tags/r#{ActiveLdap::VERSION}" end -# vim: syntax=Ruby +# vim: syntax=ruby From codesite-noreply at google.com Tue May 15 16:42:32 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 13:42:32 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r188 - tags/r0.8.2 Message-ID: <163600d40604308845d8686f1402be@google.com> Author: wad Date: Tue May 15 13:42:15 2007 New Revision: 188 Added: tags/r0.8.2/ - copied from r187, /trunk/ Log: New release tag From codesite-noreply at google.com Tue May 15 16:45:22 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 13:45:22 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r189 - tags/r0.8.2/lib Message-ID: <163600d6850430884ff787a213f45b@google.com> Author: wad Date: Tue May 15 13:45:06 2007 New Revision: 189 Modified: tags/r0.8.2/lib/active_ldap.rb Log: Update the version to 0.8.2 as it should be. Modified: tags/r0.8.2/lib/active_ldap.rb ============================================================================== --- tags/r0.8.2/lib/active_ldap.rb (original) +++ tags/r0.8.2/lib/active_ldap.rb Tue May 15 13:45:06 2007 @@ -942,7 +942,7 @@ require 'active_ldap/callbacks' module ActiveLdap - VERSION = "0.8.1" + VERSION = "0.8.2" end ActiveLdap::Base.class_eval do From codesite-noreply at google.com Tue May 15 16:46:31 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 13:46:31 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r190 - in trunk: . lib Message-ID: <163600d6850430885412cd9c13f489@google.com> Author: wad Date: Tue May 15 13:46:13 2007 New Revision: 190 Modified: trunk/CHANGES trunk/lib/active_ldap.rb Log: Update version and CHANGES - 0.8.2 Modified: trunk/CHANGES ============================================================================== --- trunk/CHANGES (original) +++ trunk/CHANGES Tue May 15 13:46:13 2007 @@ -1,3 +1,6 @@ +0.8.2: + * Added Net::LDAP support! + * Loads of bug fixes and enhancements -- see SVN for a complete history! 0.8.1: * used Dependencies.load_paths. * check whether attribute name is available or not. Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Tue May 15 13:46:13 2007 @@ -942,7 +942,7 @@ require 'active_ldap/callbacks' module ActiveLdap - VERSION = "0.8.1" + VERSION = "0.8.2" end ActiveLdap::Base.class_eval do From codesite-noreply at google.com Tue May 15 16:46:53 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 13:46:53 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r191 - tags/r0.8.2 Message-ID: <163600d8e40430885562a5f314235f@google.com> Author: wad Date: Tue May 15 13:46:35 2007 New Revision: 191 Modified: tags/r0.8.2/CHANGES Log: Update CHANGES - whoops Modified: tags/r0.8.2/CHANGES ============================================================================== --- tags/r0.8.2/CHANGES (original) +++ tags/r0.8.2/CHANGES Tue May 15 13:46:35 2007 @@ -1,3 +1,6 @@ +0.8.2: + * Added Net::LDAP support! + * Loads of bug fixes and enhancements -- see SVN for a complete history! 0.8.1: * used Dependencies.load_paths. * check whether attribute name is available or not. From codesite-noreply at google.com Tue May 15 20:04:53 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 17:04:53 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r192 - trunk Message-ID: <163600d68504308b19803ff21418e5@google.com> Author: koutou Date: Tue May 15 17:04:37 2007 New Revision: 192 Modified: trunk/CHANGES Log: * updated CHANGES between 0.8.1 and 0.8.2. Modified: trunk/CHANGES ============================================================================== --- trunk/CHANGES (original) +++ trunk/CHANGES Tue May 15 17:04:37 2007 @@ -1,6 +1,31 @@ 0.8.2: * Added Net::LDAP support! - * Loads of bug fixes and enhancements -- see SVN for a complete history! + * supported SASL Digest-MD5 authentication with Net::LDAP. + * improved LDAP server support: + * improved Sun DS support. + * improved ActiveDirectory support. Thanks to Ernie Miller! + * improved Fedora-DS support. Thanks to by Daniel Pfile! + * improved existing functions: + * improved DN handling. Thanks to James Hughes! + * improved SASL bind. + * improved old API check. + * improved schema handling. Thanks to Christoph Lipp! + * improved filter notification. + * updated documents: + * updated Rails realted documenation. Thanks to James Hughes! + * updated documentation for the changes between 0.7.1 and 0.8.0. + Thanks to Buzz Chopra! + * added new features: + * added scaffold_al generator for Rails. + * added required_classes to default filter value. Thanks to Jeff Hall! + * added :recommended_classes option to ldap_mapping. + * added :sort_by and :order options to find. + * added ActiveLdap::Base#to_param for ActionController. + * fixed some bugs: + * fixed rake install/uninstall. + * fixed typos. Thanks to Nobody! + * fixed required_classes initialization. Thanks to James Hughes! + 0.8.1: * used Dependencies.load_paths. * check whether attribute name is available or not. From codesite-noreply at google.com Tue May 15 20:47:06 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Tue, 15 May 2007 17:47:06 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r193 - trunk Message-ID: <163600d8e404308bb07e444e14504e@google.com> Author: koutou Date: Tue May 15 17:46:51 2007 New Revision: 193 Modified: trunk/Rakefile Log: * added workaround to publish RDoc to http://ruby-activeldap.rubyforge.org/doc/. Modified: trunk/Rakefile ============================================================================== --- trunk/Rakefile (original) +++ trunk/Rakefile Tue May 15 17:46:51 2007 @@ -28,6 +28,18 @@ EOF end +publish_docs_actions = task(:publish_docs).instance_variable_get("@actions") +original_project_name = nil +before_publish_docs = Proc.new do + original_project_name = project.name + project.name = "doc" +end +after_publish_docs = Proc.new do + project.name = original_project_name +end +publish_docs_actions.unshift(before_publish_docs) +publish_docs_actions.push(after_publish_docs) + # fix Hoe's incorrect guess. project.spec.executables.clear project.bin_files = project.spec.files.grep(/^bin/) From codesite-noreply at google.com Thu May 17 20:51:43 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 17 May 2007 17:51:43 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r194 - trunk/lib/active_ldap Message-ID: Author: koutou Date: Thu May 17 17:51:28 2007 New Revision: 194 Modified: trunk/lib/active_ldap/base.rb Log: * fixed variable name. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Thu May 17 17:51:28 2007 @@ -1189,8 +1189,8 @@ end def normalize_attribute_names(names) - names.flatten.uniq.collect do |may| - schema.attribute_aliases(may).first + names.flatten.uniq.collect do |name| + schema.attribute_aliases(name).first end end From codesite-noreply at google.com Thu May 17 23:58:51 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Thu, 17 May 2007 20:58:51 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r195 - trunk/lib/active_ldap Message-ID: <163600d1030430b699e8ffb61784a3@google.com> Author: koutou Date: Thu May 17 20:58:34 2007 New Revision: 195 Modified: trunk/lib/active_ldap/base.rb Log: * added workaround for a schema that has duplicated IDs. Thanks to Jacob Wilkins! Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Thu May 17 20:58:34 2007 @@ -1180,8 +1180,8 @@ @musts[objc] = attributes[:must] @mays[objc] = attributes[:may] end - @must = normalize_attribute_names(@musts.values) - @may = normalize_attribute_names(@mays.values) + @must = normalize_attribute_names(@musts.values).compact + @may = normalize_attribute_names(@mays.values).compact (@must + @may).uniq.each do |attr| # Update attr_method with appropriate define_attribute_methods(attr) From codesite-noreply at google.com Sat May 26 00:40:48 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Fri, 25 May 2007 21:40:48 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r196 - in trunk: lib/active_ldap test Message-ID: <163600d6850431581ea9288439827@google.com> Author: koutou Date: Fri May 25 21:40:41 2007 New Revision: 196 Modified: trunk/lib/active_ldap/base.rb trunk/lib/active_ldap/schema.rb trunk/test/test_schema.rb Log: * supported duplicated schema ID. Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Fri May 25 21:40:41 2007 @@ -1180,8 +1180,8 @@ @musts[objc] = attributes[:must] @mays[objc] = attributes[:may] end - @must = normalize_attribute_names(@musts.values).compact - @may = normalize_attribute_names(@mays.values).compact + @must = normalize_attribute_names(@musts.values) + @may = normalize_attribute_names(@mays.values) (@must + @may).uniq.each do |attr| # Update attr_method with appropriate define_attribute_methods(attr) @@ -1190,7 +1190,7 @@ def normalize_attribute_names(names) names.flatten.uniq.collect do |name| - schema.attribute_aliases(name).first + schema.attribute_aliases(name).first || name end end @@ -1308,7 +1308,7 @@ # onto the first attribute passed in. def define_attribute_methods(attr) logger.debug {"stub: called define_attribute_methods(#{attr.inspect})"} - return if @attr_methods.has_key? attr + return if @attr_methods.has_key?(attr) schema.attribute_aliases(attr).each do |ali| logger.debug {"associating #{ali} --> #{attr}"} @attr_methods[ali] = attr Modified: trunk/lib/active_ldap/schema.rb ============================================================================== --- trunk/lib/active_ldap/schema.rb (original) +++ trunk/lib/active_ldap/schema.rb Fri May 25 21:40:41 2007 @@ -53,10 +53,13 @@ next unless /\A\s*\(\s*(#{OID_RE})\s*(.*)\s*\)\s*\z/ =~ schema schema_id = $1 rest = $2 - next if ids.has_key?(schema_id) - attributes = {} - ids[schema_id] = attributes + if ids.has_key?(schema_id) + attributes = ids[schema_id] + else + attributes = {} + ids[schema_id] = attributes + end parse_attributes(rest, attributes) (attributes["NAME"] || []).each do |v| @@ -244,7 +247,8 @@ when no_value values = ["TRUE"] end - attributes[normalize_attribute_name(name)] = values + attributes[normalize_attribute_name(name)] ||= [] + attributes[normalize_attribute_name(name)].concat(values) end end Modified: trunk/test/test_schema.rb ============================================================================== --- trunk/test/test_schema.rb (original) +++ trunk/test/test_schema.rb Fri May 25 21:40:41 2007 @@ -2,6 +2,77 @@ class TestSchema < Test::Unit::TestCase priority :must + def test_duplicate_schema + sasNMASProductOptions_schema = + "( 2.16.840.1.113719.1.39.42.1.0.38 NAME 'sasNMASProductOptions' " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64512} SINGLE-VALUE " + + "X-NDS_PUBLIC_READ '1' )" + rADIUSActiveConnections_schema = + "( 2.16.840.1.113719.1.39.42.1.0.38 NAME 'rADIUSActiveConnections' " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64512} X-NDS_NAME " + + "'RADIUS:ActiveConnections' X-NDS_NOT_SCHED_SYNC_IMMEDIATE '1' )" + + sasNMASProductOptions = 'sasNMASProductOptions' + rADIUSActiveConnections = 'rADIUSActiveConnections' + sasNMASProductOptions_aliases = + [sasNMASProductOptions, [sasNMASProductOptions]] + rADIUSActiveConnections_aliases = + [rADIUSActiveConnections, [rADIUSActiveConnections]] + sas__sas_radius_aliases = + [sasNMASProductOptions, [sasNMASProductOptions, + rADIUSActiveConnections]] + radius__sas_radius_aliases = + [rADIUSActiveConnections, [sasNMASProductOptions, + rADIUSActiveConnections]] + sas__radius_sas_aliases = + [sasNMASProductOptions, [rADIUSActiveConnections, + sasNMASProductOptions]] + radius__radius_sas_aliases = + [rADIUSActiveConnections, [rADIUSActiveConnections, + sasNMASProductOptions]] + + assert_attribute_aliases([sasNMASProductOptions_aliases], + [sasNMASProductOptions_schema], + false) + assert_attribute_aliases([rADIUSActiveConnections_aliases], + [rADIUSActiveConnections_schema], + false) + + assert_attribute_aliases([sasNMASProductOptions_aliases, + radius__sas_radius_aliases], + [sasNMASProductOptions_schema, + rADIUSActiveConnections_schema], + false) + assert_attribute_aliases([rADIUSActiveConnections_aliases, + sas__radius_sas_aliases], + [rADIUSActiveConnections_schema, + sasNMASProductOptions_schema], + false) + + assert_attribute_aliases([radius__sas_radius_aliases, + sas__sas_radius_aliases], + [sasNMASProductOptions_schema, + rADIUSActiveConnections_schema], + false) + assert_attribute_aliases([sas__radius_sas_aliases, + radius__radius_sas_aliases], + [rADIUSActiveConnections_schema, + sasNMASProductOptions_schema], + false) + + assert_attribute_aliases([sas__sas_radius_aliases, + radius__sas_radius_aliases], + [sasNMASProductOptions_schema, + rADIUSActiveConnections_schema], + true) + assert_attribute_aliases([radius__radius_sas_aliases, + sas__radius_sas_aliases], + [rADIUSActiveConnections_schema, + sasNMASProductOptions_schema], + true) + end + + priority :normal def test_empty_schema assert_make_schema_with_empty_entries(nil) assert_make_schema_with_empty_entries({}) @@ -13,7 +84,6 @@ assert_equal([], schema["attributeTypes", "cn", "DESC"]) end - priority :normal def test_attribute_name_with_under_score top_schema = "( 2.5.6.0 NAME 'Top' STRUCTURAL MUST objectClass MAY ( " + @@ -269,5 +339,17 @@ "1.3.6.1.4.1.1466.115.121.1.5", "DESC"]) assert_equal([], schema["objectClasses", "posixAccount", "MUST"]) + end + + def assert_attribute_aliases(expected, schemata, ensure_parse) + group = 'attributeTypes' + entry = {group => schemata} + schema = ActiveLdap::Schema.new(entry) + schema.send(:ensure_parse, group) if ensure_parse + result = [] + expected.each do |key,| + result << [key, schema.attribute_aliases(key.to_s)] + end + assert_equal(expected, result) end end From codesite-noreply at google.com Sat May 26 02:46:18 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Fri, 25 May 2007 23:46:18 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r197 - in trunk/lib/active_ldap: adapter association Message-ID: Author: koutou Date: Fri May 25 23:46:12 2007 New Revision: 197 Modified: trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/association/belongs_to_many.rb trunk/lib/active_ldap/association/has_many.rb Log: * don't find if there is no conditions for assosiation. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Fri May 25 23:46:12 2007 @@ -115,7 +115,7 @@ end def search(options={}) - filter = parse_filter(options[:filter] || 'objectClass=*') + filter = parse_filter(options[:filter]) || 'objectClass=*' attrs = options[:attributes] || [] scope = ensure_scope(options[:scope]) base = options[:base] Modified: trunk/lib/active_ldap/association/belongs_to_many.rb ============================================================================== --- trunk/lib/active_ldap/association/belongs_to_many.rb (original) +++ trunk/lib/active_ldap/association/belongs_to_many.rb Fri May 25 23:46:12 2007 @@ -32,7 +32,11 @@ components = values.collect do |value| [key, value] end - foreign_class.find(:all, :filter => [:or, *components]) + if components.empty? + [] + else + foreign_class.find(:all, :filter => [:or, *components]) + end end end end Modified: trunk/lib/active_ldap/association/has_many.rb ============================================================================== --- trunk/lib/active_ldap/association/has_many.rb (original) +++ trunk/lib/active_ldap/association/has_many.rb Fri May 25 23:46:12 2007 @@ -22,7 +22,11 @@ end.reject do |key, val| key.nil? or val.nil? end - foreign_class.find(:all, :filter => [:or, *components]) + if components.empty? + [] + else + foreign_class.find(:all, :filter => [:or, *components]) + end end def delete_entries(entries) From codesite-noreply at google.com Sat May 26 03:23:46 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Sat, 26 May 2007 00:23:46 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r198 - in trunk: lib/active_ldap lib/active_ldap/adapter test Message-ID: Author: koutou Date: Sat May 26 00:23:39 2007 New Revision: 198 Modified: trunk/lib/active_ldap/adapter/base.rb trunk/lib/active_ldap/base.rb trunk/test/test_adapter.rb Log: * improved filter handling. Modified: trunk/lib/active_ldap/adapter/base.rb ============================================================================== --- trunk/lib/active_ldap/adapter/base.rb (original) +++ trunk/lib/active_ldap/adapter/base.rb Sat May 26 00:23:39 2007 @@ -309,7 +309,9 @@ components = components.collect do |component| if component.is_a?(Array) and component.size == 2 key, value = component - if filter_logical_operator?(key) or value.is_a?(Hash) + if filter_logical_operator?(key) + parse_filter(component) + elsif value.is_a?(Hash) parse_filter(value, key) else construct_component(key, value, operator) Modified: trunk/lib/active_ldap/base.rb ============================================================================== --- trunk/lib/active_ldap/base.rb (original) +++ trunk/lib/active_ldap/base.rb Sat May 26 00:23:39 2007 @@ -278,10 +278,8 @@ _attr, value, _prefix = split_search_value(value) attr ||= _attr || dn_attribute || "objectClass" prefix ||= _prefix - if filter.nil? - filter = "(#{attr}=#{escape_filter_value(value, true)})" - filter = "(&#{filter}#{object_class_filters(classes)})" - end + filter ||= "(#{attr}=#{escape_filter_value(value, true)})" + filter = [:and, filter, *object_class_filters(classes)] _base = [prefix, base].compact.reject{|x| x.empty?}.join(",") search_options = { :base => _base, @@ -534,8 +532,8 @@ def object_class_filters(classes=nil) (classes || required_classes).collect do |name| - "(objectClass=#{escape_filter_value(name, true)})" - end.join("") + ["objectClass", escape_filter_value(name, true)] + end end def find_initial(options) @@ -595,12 +593,8 @@ def find_one(dn, options) attr, value, prefix = split_search_value(dn) - filters = [ - "(#{attr || dn_attribute}=#{escape_filter_value(value, true)})", - object_class_filters(options[:classes]), - options[:filter], - ] - filter = "(&#{filters.compact.join('')})" + filter = [attr || dn_attribute, escape_filter_value(value, true)] + filter = [:and, filter, options[:filter]] if options[:filter] options = {:prefix => prefix}.merge(options.merge(:filter => filter)) result = find_initial(options) if result @@ -616,18 +610,16 @@ dn_filters = dns.collect do |dn| attr, value, prefix = split_search_value(dn) attr ||= dn_attribute - filter = "(#{attr}=#{escape_filter_value(value, true)})" + filter = [attr, escape_filter_value(value, true)] if prefix - filter = "(&#{filter}(dn=*,#{escape_filter_value(prefix)},#{base}))" + filter = [:and, + filter, + [dn, "*,#{escape_filter_value(prefix)},#{base}"]] end filter end - filters = [ - "(|#{dn_filters.join('')})", - object_class_filters(options[:classes]), - options[:filter], - ] - filter = "(&#{filters.compact.join('')})" + filter = [:or, *dn_filters] + filter = [:and, filter, options[:filter]] if options[:filter] result = find_every(options.merge(:filter => filter)) if result.size == dns.size result Modified: trunk/test/test_adapter.rb ============================================================================== --- trunk/test/test_adapter.rb (original) +++ trunk/test/test_adapter.rb Sat May 26 00:23:39 2007 @@ -64,6 +64,12 @@ [:and, [:and, {"gidNumber"=>["100001"]}], [:or, {"uid"=>["temp-user1", "temp-user2"]}]]) + assert_parse_filter("(&(gidNumber=100001)" + + "(objectClass=person)(objectClass=posixAccount))", + [:and, + [:or, ["gidNumber", "100001"]], + ["objectClass", "person"], + ["objectClass", "posixAccount"]]) end def test_invalid_operator From codesite-noreply at google.com Wed May 30 19:51:50 2007 From: codesite-noreply at google.com (codesite-noreply at google.com) Date: Wed, 30 May 2007 16:51:50 -0700 Subject: [Ruby-activeldap-commit] [ruby-activeldap commit] r199 - trunk/lib Message-ID: Author: koutou Date: Wed May 30 16:51:46 2007 New Revision: 199 Modified: trunk/lib/active_ldap.rb Log: * used 'gem' instead of 'require_gem' Modified: trunk/lib/active_ldap.rb ============================================================================== --- trunk/lib/active_ldap.rb (original) +++ trunk/lib/active_ldap.rb Wed May 30 16:51:46 2007 @@ -918,7 +918,7 @@ require library_name rescue LoadError require 'rubygems' - require_gem gem_name + gem gem_name require library_name end end