treequel 1.2.2 → 1.3.0pre384
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
    
        data/lib/treequel.rb
    CHANGED
    
    | @@ -25,7 +25,7 @@ end | |
| 25 25 |  | 
| 26 26 | 
             
            # A library for interacting with LDAP modelled after Sequel.
         | 
| 27 27 | 
             
            #
         | 
| 28 | 
            -
            # @version 1. | 
| 28 | 
            +
            # @version 1.3.0
         | 
| 29 29 | 
             
            # 
         | 
| 30 30 | 
             
            # @example
         | 
| 31 31 | 
             
            #   # Connect to the directory at the specified URL
         | 
| @@ -53,10 +53,10 @@ end | |
| 53 53 | 
             
            module Treequel
         | 
| 54 54 |  | 
| 55 55 | 
             
            	# Library version
         | 
| 56 | 
            -
            	VERSION = '1. | 
| 56 | 
            +
            	VERSION = '1.3.0'
         | 
| 57 57 |  | 
| 58 58 | 
             
            	# VCS revision
         | 
| 59 | 
            -
            	REVISION = %q$Revision:  | 
| 59 | 
            +
            	REVISION = %q$Revision: 554028334395 $
         | 
| 60 60 |  | 
| 61 61 | 
             
            	# Common paths for ldap.conf
         | 
| 62 62 | 
             
            	COMMON_LDAP_CONF_PATHS = %w[
         | 
| @@ -80,6 +80,16 @@ module Treequel | |
| 80 80 |  | 
| 81 81 |  | 
| 82 82 | 
             
            	### Logging
         | 
| 83 | 
            +
            	# Log levels
         | 
| 84 | 
            +
            	LOG_LEVELS = {
         | 
| 85 | 
            +
            		'debug' => Logger::DEBUG,
         | 
| 86 | 
            +
            		'info'  => Logger::INFO,
         | 
| 87 | 
            +
            		'warn'  => Logger::WARN,
         | 
| 88 | 
            +
            		'error' => Logger::ERROR,
         | 
| 89 | 
            +
            		'fatal' => Logger::FATAL,
         | 
| 90 | 
            +
            	}.freeze
         | 
| 91 | 
            +
            	LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
         | 
| 92 | 
            +
             | 
| 83 93 | 
             
            	@default_logger = Logger.new( $stderr )
         | 
| 84 94 | 
             
            	@default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
         | 
| 85 95 |  | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rspec'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'treequel'
         | 
| 6 | 
            +
            require 'treequel/control'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            # This is a shared behavior for specs which different Treequel::Controls share in 
         | 
| 10 | 
            +
            # common. If you're creating a Treequel::Control implementation, you can test
         | 
| 11 | 
            +
            # its conformity to the expectations placed on them by adding this to your spec:
         | 
| 12 | 
            +
            # 
         | 
| 13 | 
            +
            #    require 'treequel/behavior/control'
         | 
| 14 | 
            +
            #
         | 
| 15 | 
            +
            #    describe YourControl do
         | 
| 16 | 
            +
            #
         | 
| 17 | 
            +
            #      it_should_behave_like "A Treequel::Control"
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            #    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            shared_examples_for "A Treequel::Control" do
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            	let( :control ) do
         | 
| 24 | 
            +
            		described_class
         | 
| 25 | 
            +
            	end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
             | 
| 28 | 
            +
            	it "implements one of either #get_client_controls or #get_server_controls" do
         | 
| 29 | 
            +
            		methods = [
         | 
| 30 | 
            +
            			'get_client_controls',		# 1.8.x
         | 
| 31 | 
            +
            			'get_server_controls',
         | 
| 32 | 
            +
            			:get_client_controls,		# 1.9.x
         | 
| 33 | 
            +
            			:get_server_controls
         | 
| 34 | 
            +
            		]
         | 
| 35 | 
            +
            		(control.instance_methods( false ) | methods).should_not be_empty()
         | 
| 36 | 
            +
            	end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
             | 
    
        data/lib/treequel/branch.rb
    CHANGED
    
    | @@ -38,11 +38,17 @@ class Treequel::Branch | |
| 38 38 | 
             
            	# [Boolean] Whether or not to include operational attributes by default.
         | 
| 39 39 | 
             
            	@include_operational_attrs = false
         | 
| 40 40 |  | 
| 41 | 
            +
            	# [Boolean] Whether or not to freeze values cached in @values. This helps
         | 
| 42 | 
            +
            	#   prevent you from accidentally doing branch[:attr] << 'value', which
         | 
| 43 | 
            +
            	#   modifies the cached values, but not the entry.
         | 
| 44 | 
            +
            	@freeze_converted_values = true
         | 
| 45 | 
            +
             | 
| 41 46 | 
             
            	# Whether or not to include operational attributes when fetching the
         | 
| 42 47 | 
             
            	# entry for branches.
         | 
| 43 48 | 
             
            	class << self
         | 
| 44 49 | 
             
            		extend Treequel::AttributeDeclarations
         | 
| 45 50 | 
             
            		predicate_attr :include_operational_attrs
         | 
| 51 | 
            +
            		predicate_attr :freeze_converted_values
         | 
| 46 52 | 
             
            	end
         | 
| 47 53 |  | 
| 48 54 |  | 
| @@ -54,8 +60,12 @@ class Treequel::Branch | |
| 54 60 | 
             
            	### @return [Treequel::Branch]  The new branch object.
         | 
| 55 61 | 
             
            	def self::new_from_entry( entry, directory )
         | 
| 56 62 | 
             
            		entry = Treequel::HashUtilities.stringify_keys( entry )
         | 
| 57 | 
            -
            		 | 
| 58 | 
            -
             | 
| 63 | 
            +
            		dnvals = entry.delete( 'dn' ) or
         | 
| 64 | 
            +
            			raise ArgumentError, "no 'dn' attribute for entry"
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            		Treequel.logger.debug "Creating Branch from entry: %p in directory: %s" %
         | 
| 67 | 
            +
            			[ dnvals.first, directory ]
         | 
| 68 | 
            +
            		return self.new( directory, dnvals.first, entry )
         | 
| 59 69 | 
             
            	end
         | 
| 60 70 |  | 
| 61 71 |  | 
| @@ -72,6 +82,7 @@ class Treequel::Branch | |
| 72 82 | 
             
            	### @param [String] dn  The DN of the entry the Branch is wrapping.
         | 
| 73 83 | 
             
            	### @param [LDAP::Entry, Hash] entry  The entry object if it's already been fetched.
         | 
| 74 84 | 
             
            	def initialize( directory, dn, entry=nil )
         | 
| 85 | 
            +
            		raise ArgumentError, "nil DN" unless dn
         | 
| 75 86 | 
             
            		raise ArgumentError, "invalid DN" unless
         | 
| 76 87 | 
             
            			dn.match( Patterns::DISTINGUISHED_NAME ) || dn.empty?
         | 
| 77 88 | 
             
            		raise ArgumentError, "can't cast a %s to an LDAP::Entry" % [entry.class.name] unless
         | 
| @@ -93,7 +104,7 @@ class Treequel::Branch | |
| 93 104 | 
             
            	######
         | 
| 94 105 |  | 
| 95 106 | 
             
            	# Delegate some other methods to a new Branchset via the #branchset method
         | 
| 96 | 
            -
            	def_method_delegators :branchset, :filter, :scope, :select, :limit, :timeout, :order
         | 
| 107 | 
            +
            	def_method_delegators :branchset, :filter, :scope, :select, :limit, :timeout, :order, :as
         | 
| 97 108 |  | 
| 98 109 | 
             
            	# Delegate some methods to the Branch's directory via its accessor
         | 
| 99 110 | 
             
            	def_method_delegators :directory, :controls, :referrals
         | 
| @@ -204,7 +215,8 @@ class Treequel::Branch | |
| 204 215 | 
             
            	### Return the Branch's immediate parent node.
         | 
| 205 216 | 
             
            	### @return [Treequel::Branch]
         | 
| 206 217 | 
             
            	def parent
         | 
| 207 | 
            -
            		 | 
| 218 | 
            +
            		pardn = self.parent_dn or return nil
         | 
| 219 | 
            +
            		return self.class.new( self.directory, pardn )
         | 
| 208 220 | 
             
            	end
         | 
| 209 221 |  | 
| 210 222 |  | 
| @@ -267,7 +279,7 @@ class Treequel::Branch | |
| 267 279 | 
             
            		self.log.debug "  making LDIF from an entry: %p" % [ entry ]
         | 
| 268 280 |  | 
| 269 281 | 
             
            		entry.keys.reject {|k| k == 'dn' }.each do |attribute|
         | 
| 270 | 
            -
            			entry[ | 
| 282 | 
            +
            			Array( entry[attribute] ).each do |val|
         | 
| 271 283 | 
             
            				ldif << ldif_for_attr( attribute, val, width )
         | 
| 272 284 | 
             
            			end
         | 
| 273 285 | 
             
            		end
         | 
| @@ -299,14 +311,16 @@ class Treequel::Branch | |
| 299 311 | 
             
            	def []( attrname )
         | 
| 300 312 | 
             
            		attrsym = attrname.to_sym
         | 
| 301 313 |  | 
| 302 | 
            -
            		 | 
| 314 | 
            +
            		if @values.key?( attrsym )
         | 
| 315 | 
            +
            			self.log.debug "  value for %p is cached (%p)." % [ attrname, @values[attrsym] ]
         | 
| 316 | 
            +
            		else
         | 
| 303 317 | 
             
            			self.log.debug "  value for %p is NOT cached." % [ attrsym ]
         | 
| 304 318 | 
             
            			value = self.get_converted_object( attrsym )
         | 
| 305 319 | 
             
            			self.log.debug "  converted value is: %p" % [ value ]
         | 
| 306 | 
            -
            			value.freeze if | 
| 320 | 
            +
            			value.freeze if
         | 
| 321 | 
            +
            				self.class.freeze_converted_values? &&
         | 
| 322 | 
            +
            				value.respond_to?( :freeze )
         | 
| 307 323 | 
             
            			@values[ attrsym ] = value
         | 
| 308 | 
            -
            		else
         | 
| 309 | 
            -
            			self.log.debug "  value for %p is cached." % [ attrname ]
         | 
| 310 324 | 
             
            		end
         | 
| 311 325 |  | 
| 312 326 | 
             
            		return @values[ attrsym ]
         | 
| @@ -366,23 +380,33 @@ class Treequel::Branch | |
| 366 380 | 
             
            	### 
         | 
| 367 381 | 
             
            	### @return [TrueClass] if the delete succeeded
         | 
| 368 382 | 
             
            	def delete( *attributes )
         | 
| 383 | 
            +
             | 
| 384 | 
            +
            		# If no attributes are given, delete the whole entry
         | 
| 369 385 | 
             
            		if attributes.empty?
         | 
| 370 386 | 
             
            			self.log.info "No attributes specified; deleting entire entry for %s" % [ self.dn ]
         | 
| 371 387 | 
             
            			self.directory.delete( self )
         | 
| 388 | 
            +
             | 
| 389 | 
            +
            		# Otherwise, gather up the LDAP::Mod objects that will delete the given attributes
         | 
| 372 390 | 
             
            		else
         | 
| 373 391 | 
             
            			self.log.debug "Deleting attributes: %p" % [ attributes ]
         | 
| 374 392 | 
             
            			mods = attributes.flatten.collect do |attribute|
         | 
| 393 | 
            +
             | 
| 394 | 
            +
            				# Delete particular values of the attribute
         | 
| 375 395 | 
             
            				if attribute.is_a?( Hash )
         | 
| 376 396 | 
             
            					attribute.collect do |key,vals|
         | 
| 377 | 
            -
            						vals =  | 
| 397 | 
            +
            						vals = [ vals ] unless vals.is_a?( Array )
         | 
| 398 | 
            +
            						vals.collect! {|val| self.get_converted_attribute(key, val) }
         | 
| 378 399 | 
             
            						LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, key.to_s, vals )
         | 
| 379 400 | 
             
            					end
         | 
| 401 | 
            +
             | 
| 402 | 
            +
            				# Delete all values of the attribute
         | 
| 380 403 | 
             
            				else
         | 
| 381 404 | 
             
            					LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, attribute.to_s, [] )
         | 
| 382 405 | 
             
            				end
         | 
| 383 | 
            -
            			end.flatten
         | 
| 384 406 |  | 
| 385 | 
            -
            			 | 
| 407 | 
            +
            			end
         | 
| 408 | 
            +
             | 
| 409 | 
            +
            			self.directory.modify( self, mods.flatten )
         | 
| 386 410 | 
             
            		end
         | 
| 387 411 |  | 
| 388 412 | 
             
            		self.clear_caches
         | 
| @@ -394,9 +418,10 @@ class Treequel::Branch | |
| 394 418 | 
             
            	### Create the entry for this Branch with the specified +attributes+. The +attributes+ should,
         | 
| 395 419 | 
             
            	### at a minimum, contain the pair `:objectClass => :someStructuralObjectClass`.
         | 
| 396 420 | 
             
            	### 
         | 
| 397 | 
            -
            	### @ | 
| 421 | 
            +
            	### @see Treequel::Directory#create
         | 
| 398 422 | 
             
            	def create( attributes={} )
         | 
| 399 423 | 
             
            		self.directory.create( self, attributes )
         | 
| 424 | 
            +
            		self.clear_caches
         | 
| 400 425 | 
             
            		return self
         | 
| 401 426 | 
             
            	end
         | 
| 402 427 |  | 
| @@ -432,7 +457,10 @@ class Treequel::Branch | |
| 432 457 | 
             
            	### @param [Hash<String, Symbol => Object>] attributes 
         | 
| 433 458 | 
             
            	def move( rdn )
         | 
| 434 459 | 
             
            		self.log.debug "Asking the directory to move me to an entry called %p" % [ rdn ]
         | 
| 435 | 
            -
            		 | 
| 460 | 
            +
            		self.directory.move( self, rdn )
         | 
| 461 | 
            +
            		self.clear_caches
         | 
| 462 | 
            +
             | 
| 463 | 
            +
            		return self
         | 
| 436 464 | 
             
            	end
         | 
| 437 465 |  | 
| 438 466 |  | 
| @@ -493,9 +521,8 @@ class Treequel::Branch | |
| 493 521 | 
             
            		schema = self.directory.schema
         | 
| 494 522 |  | 
| 495 523 | 
             
            		oc_oids = self[:objectClass] || []
         | 
| 496 | 
            -
            		self.log.debug "  objectClass OIDs are: %p" % [ oc_oids ]
         | 
| 497 524 | 
             
            		oc_oids |= additional_classes.collect {|str| str.to_sym }
         | 
| 498 | 
            -
            		oc_oids <<  | 
| 525 | 
            +
            		oc_oids << 'top' if oc_oids.empty?
         | 
| 499 526 |  | 
| 500 527 | 
             
            		oclasses = []
         | 
| 501 528 | 
             
            		oc_oids.each do |oid|
         | 
| @@ -504,7 +531,7 @@ class Treequel::Branch | |
| 504 531 | 
             
            			oclasses << oc
         | 
| 505 532 | 
             
            		end
         | 
| 506 533 |  | 
| 507 | 
            -
            		self.log.debug "  found %d objectClasses: %p" % [  oclasses.length, oclasses ]
         | 
| 534 | 
            +
            		self.log.debug "  found %d objectClasses: %p" % [  oclasses.length, oclasses.map(&:name) ]
         | 
| 508 535 | 
             
            		return oclasses.uniq
         | 
| 509 536 | 
             
            	end
         | 
| 510 537 |  | 
| @@ -539,7 +566,7 @@ class Treequel::Branch | |
| 539 566 | 
             
            		 	[ oclasses.map(&:name) ]
         | 
| 540 567 |  | 
| 541 568 | 
             
            		oclasses.each do |oc|
         | 
| 542 | 
            -
            			self.log.debug "  adding %p from %p" % [ oc.must, oc ]
         | 
| 569 | 
            +
            			self.log.debug "  adding %p from %p" % [ oc.must.map(&:name), oc.name ]
         | 
| 543 570 | 
             
            			types |= oc.must
         | 
| 544 571 | 
             
            		end
         | 
| 545 572 |  | 
| @@ -571,10 +598,10 @@ class Treequel::Branch | |
| 571 598 | 
             
            		attrhash = {}
         | 
| 572 599 |  | 
| 573 600 | 
             
            		self.must_attribute_types( *additional_object_classes ).each do |attrtype|
         | 
| 574 | 
            -
            			self.log.debug "  adding attrtype %p to the MUST attributes hash" % [ attrtype ]
         | 
| 601 | 
            +
            			# self.log.debug "  adding attrtype %p to the MUST attributes hash" % [ attrtype.name ]
         | 
| 575 602 |  | 
| 576 603 | 
             
            			if attrtype.name == :objectClass
         | 
| 577 | 
            -
            				attrhash[ :objectClass ] = [ | 
| 604 | 
            +
            				attrhash[ :objectClass ] = ['top'] | additional_object_classes
         | 
| 578 605 | 
             
            			elsif attrtype.single?
         | 
| 579 606 | 
             
            				attrhash[ attrtype.name ] = ''
         | 
| 580 607 | 
             
            			else
         | 
| @@ -625,7 +652,7 @@ class Treequel::Branch | |
| 625 652 | 
             
            		attrhash = {}
         | 
| 626 653 |  | 
| 627 654 | 
             
            		self.may_attribute_types( *additional_object_classes ).each do |attrtype|
         | 
| 628 | 
            -
            			self.log.debug "  adding attrtype %p to the MAY attributes hash" % [ attrtype ]
         | 
| 655 | 
            +
            			# self.log.debug "  adding attrtype %p to the MAY attributes hash" % [ attrtype.named ]
         | 
| 629 656 |  | 
| 630 657 | 
             
            			if attrtype.single?
         | 
| 631 658 | 
             
            				attrhash[ attrtype.name ] = nil
         | 
| @@ -772,15 +799,16 @@ class Treequel::Branch | |
| 772 799 | 
             
            	### Get the value associated with +attrsym+, convert it to a Ruby object if the Branch's
         | 
| 773 800 | 
             
            	### directory has a conversion rule, and return it.
         | 
| 774 801 | 
             
            	def get_converted_object( attrsym )
         | 
| 775 | 
            -
            		 | 
| 776 | 
            -
            		value = self.entry[ attrsym.to_s ] or return nil
         | 
| 802 | 
            +
            		value = self.entry ? self.entry[ attrsym.to_s ] : nil
         | 
| 777 803 |  | 
| 778 804 | 
             
            		if attribute = self.directory.schema.attribute_types[ attrsym ]
         | 
| 805 | 
            +
            			syntax_oid = attribute.syntax.oid
         | 
| 806 | 
            +
             | 
| 779 807 | 
             
            			if attribute.single?
         | 
| 780 | 
            -
            				value = self.directory.convert_to_object(  | 
| 808 | 
            +
            				value = self.directory.convert_to_object( syntax_oid, value.first ) if value
         | 
| 781 809 | 
             
            			else
         | 
| 782 | 
            -
            				value = value.collect do |raw|
         | 
| 783 | 
            -
            					self.directory.convert_to_object(  | 
| 810 | 
            +
            				value = Array( value ).collect do |raw|
         | 
| 811 | 
            +
            					self.directory.convert_to_object( syntax_oid, raw )
         | 
| 784 812 | 
             
            				end
         | 
| 785 813 | 
             
            			end
         | 
| 786 814 | 
             
            		else
         | 
| @@ -795,8 +823,8 @@ class Treequel::Branch | |
| 795 823 | 
             
            	### and return it.
         | 
| 796 824 | 
             
            	def get_converted_attribute( attrsym, object )
         | 
| 797 825 | 
             
            		if attribute = self.directory.schema.attribute_types[ attrsym ]
         | 
| 798 | 
            -
            			self.log.debug "converting %p object to a %p attribute" %
         | 
| 799 | 
            -
            				[ attrsym, attribute.syntax.desc ]
         | 
| 826 | 
            +
            			self.log.debug "converting %p object (a %p) to a %p attribute" %
         | 
| 827 | 
            +
            				[ attrsym, object.class, attribute.syntax.desc ]
         | 
| 800 828 | 
             
            			return self.directory.convert_to_attribute( attribute.syntax.oid, object )
         | 
| 801 829 | 
             
            		else
         | 
| 802 830 | 
             
            			self.log.info "no attributeType for %p" % [ attrsym ]
         | 
    
        data/lib/treequel/branchset.rb
    CHANGED
    
    | @@ -77,9 +77,17 @@ class Treequel::Branchset | |
| 77 77 | 
             
            	def initialize( branch, options={} )
         | 
| 78 78 | 
             
            		@branch = branch
         | 
| 79 79 | 
             
            		@options = DEFAULT_OPTIONS.merge( options )
         | 
| 80 | 
            +
            		self.log.debug "Setting up %p for branch %p with options: %p" %
         | 
| 81 | 
            +
            			[ self.class, @branch, @options ]
         | 
| 80 82 |  | 
| 81 | 
            -
            		 | 
| 82 | 
            -
            			 | 
| 83 | 
            +
            		if @branch.directory.registered_controls.empty?
         | 
| 84 | 
            +
            			self.log.debug "  no registered controls."
         | 
| 85 | 
            +
            		else
         | 
| 86 | 
            +
            			@branch.directory.registered_controls.each do |control|
         | 
| 87 | 
            +
            				self.log.debug "  extending with %p" % [ control ]
         | 
| 88 | 
            +
            				self.extend( control )
         | 
| 89 | 
            +
            			end
         | 
| 90 | 
            +
            		end
         | 
| 83 91 |  | 
| 84 92 | 
             
            		super()
         | 
| 85 93 | 
             
            	end
         | 
| @@ -47,7 +47,7 @@ module Treequel::PagedResultsControl | |
| 47 47 |  | 
| 48 48 | 
             
            	### Add the control's instance variables to including Branchsets.
         | 
| 49 49 | 
             
            	def initialize
         | 
| 50 | 
            -
            		@paged_results_cookie =  | 
| 50 | 
            +
            		@paged_results_cookie = nil
         | 
| 51 51 | 
             
            		@paged_results_setsize = nil
         | 
| 52 52 | 
             
            	end
         | 
| 53 53 |  | 
| @@ -75,8 +75,10 @@ module Treequel::PagedResultsControl | |
| 75 75 | 
             
            		newset = self.clone
         | 
| 76 76 |  | 
| 77 77 | 
             
            		if setsize.nil? || setsize.zero?
         | 
| 78 | 
            +
            			self.log.debug "Removing paged results control."
         | 
| 78 79 | 
             
            			newset.paged_results_setsize = nil
         | 
| 79 80 | 
             
            		else
         | 
| 81 | 
            +
            			self.log.debug "Adding paged results control with page size = %d." % [ setsize ]
         | 
| 80 82 | 
             
            			newset.paged_results_setsize = setsize
         | 
| 81 83 | 
             
            		end
         | 
| 82 84 |  | 
| @@ -96,7 +98,7 @@ module Treequel::PagedResultsControl | |
| 96 98 | 
             
            	### Remove any paging control associated with the receiving Branchset.
         | 
| 97 99 | 
             
            	### @return [void]
         | 
| 98 100 | 
             
            	def without_paging!
         | 
| 99 | 
            -
            		self.paged_results_cookie =  | 
| 101 | 
            +
            		self.paged_results_cookie = nil
         | 
| 100 102 | 
             
            		self.paged_results_setsize = nil
         | 
| 101 103 | 
             
            	end
         | 
| 102 104 |  | 
    
        data/lib/treequel/directory.rb
    CHANGED
    
    | @@ -99,7 +99,7 @@ class Treequel::Directory | |
| 99 99 |  | 
| 100 100 |  | 
| 101 101 | 
             
            	#################################################################
         | 
| 102 | 
            -
            	###	 | 
| 102 | 
            +
            	###	I N S T A N C E   M E T H O D S
         | 
| 103 103 | 
             
            	#################################################################
         | 
| 104 104 |  | 
| 105 105 | 
             
            	### Create a new Treequel::Directory with the given +options+. Options is a hash with one
         | 
| @@ -132,14 +132,14 @@ class Treequel::Directory | |
| 132 132 | 
             
            		@conn                  = nil
         | 
| 133 133 | 
             
            		@bound_user            = nil
         | 
| 134 134 |  | 
| 135 | 
            -
            		@base_dn               = options[:base_dn] || self.get_default_base_dn
         | 
| 136 | 
            -
             | 
| 137 | 
            -
            		@base                  = nil
         | 
| 138 135 |  | 
| 139 136 | 
             
            		@object_conversions    = DEFAULT_OBJECT_CONVERSIONS.dup
         | 
| 140 137 | 
             
            		@attribute_conversions = DEFAULT_ATTRIBUTE_CONVERSIONS.dup
         | 
| 141 138 | 
             
            		@registered_controls   = []
         | 
| 142 139 |  | 
| 140 | 
            +
            		@base_dn               = options[:base_dn] || self.get_default_base_dn
         | 
| 141 | 
            +
            		@base                  = nil
         | 
| 142 | 
            +
             | 
| 143 143 | 
             
            		# Immediately bind if credentials are passed to the initializer.
         | 
| 144 144 | 
             
            		if ( options[:bind_dn] && options[:pass] )
         | 
| 145 145 | 
             
            			self.bind( options[:bind_dn], options[:pass] )
         | 
| @@ -155,7 +155,7 @@ class Treequel::Directory | |
| 155 155 | 
             
            	def_method_delegators :base, *DELEGATED_BRANCH_METHODS
         | 
| 156 156 |  | 
| 157 157 | 
             
            	# Delegate some methods to the connection via the #conn method
         | 
| 158 | 
            -
            	def_method_delegators :conn, :controls, :referrals | 
| 158 | 
            +
            	def_method_delegators :conn, :controls, :referrals
         | 
| 159 159 |  | 
| 160 160 |  | 
| 161 161 | 
             
            	# The host to connect to.
         | 
| @@ -187,6 +187,12 @@ class Treequel::Directory | |
| 187 187 | 
             
            	attr_reader :bound_user
         | 
| 188 188 |  | 
| 189 189 |  | 
| 190 | 
            +
            	### Fetch the root DSE as a Treequel::Branch.
         | 
| 191 | 
            +
            	def root_dse
         | 
| 192 | 
            +
            		return self.search( '', :base, '(objectClass=*)', :selectattrs => ['+'] ).first
         | 
| 193 | 
            +
            	end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
             | 
| 190 196 | 
             
            	### Fetch the Branch for the base node of the directory.
         | 
| 191 197 | 
             
            	### @return [Treequel::Branch]
         | 
| 192 198 | 
             
            	def base
         | 
| @@ -231,6 +237,23 @@ class Treequel::Directory | |
| 231 237 | 
             
            	end
         | 
| 232 238 |  | 
| 233 239 |  | 
| 240 | 
            +
            	### Drop the existing connection and establish a new one.
         | 
| 241 | 
            +
            	### @return [Boolean]  +true+ if the connection was re-established
         | 
| 242 | 
            +
            	### @raise [RuntimeError]  if the re-connection failed
         | 
| 243 | 
            +
            	def reconnect
         | 
| 244 | 
            +
            		self.log.info "Reconnecting to %s..." % [ self.uri ]
         | 
| 245 | 
            +
            		@conn = self.connect
         | 
| 246 | 
            +
            		self.log.info "...reconnected."
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            		return true
         | 
| 249 | 
            +
            	rescue LDAP::ResultError => err
         | 
| 250 | 
            +
            		self.log.error "%s while attempting to reconnect to %s: %s" %
         | 
| 251 | 
            +
            			[ err.class.name, self.uri, err.message ]
         | 
| 252 | 
            +
            		raise "Couldn't reconnect to %s: %s: %s" %
         | 
| 253 | 
            +
            			[ self.uri, err.class.name, err.message ]
         | 
| 254 | 
            +
            	end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
             | 
| 234 257 | 
             
            	### Return the URI object that corresponds to the directory.
         | 
| 235 258 | 
             
            	### @return [URI::LDAP]
         | 
| 236 259 | 
             
            	def uri
         | 
| @@ -457,7 +480,7 @@ class Treequel::Directory | |
| 457 480 | 
             
            			self.log.debug "Modifying %s with LDAP mod objects: %p" % [ branch.dn, mods ]
         | 
| 458 481 | 
             
            			self.conn.modify( branch.dn, mods )
         | 
| 459 482 | 
             
            		else
         | 
| 460 | 
            -
            			normattrs =  | 
| 483 | 
            +
            			normattrs = normalize_attributes( mods )
         | 
| 461 484 | 
             
            			self.log.debug "Modifying %s with: %p" % [ branch.dn, normattrs ]
         | 
| 462 485 | 
             
            			self.conn.modify( branch.dn, normattrs )
         | 
| 463 486 | 
             
            		end
         | 
| @@ -472,28 +495,13 @@ class Treequel::Directory | |
| 472 495 |  | 
| 473 496 |  | 
| 474 497 | 
             
            	### Create the entry for the given +branch+, setting its attributes to +newattrs+.
         | 
| 498 | 
            +
            	### @param [Treequel::Branch, #to_s] branch   the branch to create (or a DN string)
         | 
| 499 | 
            +
            	### @param [Hash, Array<LDAP::Mod>] newattrs  the attributes to create the entry with. This
         | 
| 500 | 
            +
            	###                                   can be either a Hash of attributes, or an Array of
         | 
| 501 | 
            +
            	###                                   LDAP::Mod objects.
         | 
| 475 502 | 
             
            	def create( branch, newattrs={} )
         | 
| 476 | 
            -
            		 | 
| 477 | 
            -
            		 | 
| 478 | 
            -
             | 
| 479 | 
            -
            		# Merge RDN attributes with existing ones, combining any that exist in both
         | 
| 480 | 
            -
            		self.log.debug "Smushing rdn attributes %p into %p" % [ branch.rdn_attributes, newdn ]
         | 
| 481 | 
            -
            		newattrs.merge!( branch.rdn_attributes ) do |key, *values|
         | 
| 482 | 
            -
            			values.flatten.uniq
         | 
| 483 | 
            -
            		end
         | 
| 484 | 
            -
             | 
| 485 | 
            -
            		normattrs = self.normalize_attributes( newattrs )
         | 
| 486 | 
            -
            		raise ArgumentError, "Can't create an entry with no objectClasses" unless
         | 
| 487 | 
            -
            			normattrs.key?( 'objectClass' )
         | 
| 488 | 
            -
            		normattrs['objectClass'].each do |oc|
         | 
| 489 | 
            -
            			raise ArgumentError, "No such objectClass #{oc.inspect}" unless
         | 
| 490 | 
            -
            				schema.object_classes.key?(oc.to_sym)
         | 
| 491 | 
            -
            		end
         | 
| 492 | 
            -
            		raise ArgumentError, "Can't create an entry with no structural objectClass" unless
         | 
| 493 | 
            -
            			normattrs['objectClass'].any? {|oc| schema.object_classes[oc.to_sym].structural? }
         | 
| 494 | 
            -
             | 
| 495 | 
            -
            		self.log.debug "Creating an entry at %s with the attributes: %p" % [ newdn, normattrs ]
         | 
| 496 | 
            -
            		self.conn.add( newdn, normattrs )
         | 
| 503 | 
            +
            		newattrs = normalize_attributes( newattrs ) if newattrs.is_a?( Hash )
         | 
| 504 | 
            +
            		self.conn.add( branch.to_s, newattrs )
         | 
| 497 505 |  | 
| 498 506 | 
             
            		return true
         | 
| 499 507 | 
             
            	end
         | 
| @@ -565,6 +573,7 @@ class Treequel::Directory | |
| 565 573 | 
             
            			end
         | 
| 566 574 | 
             
            		end
         | 
| 567 575 | 
             
            	end
         | 
| 576 | 
            +
            	alias_method :register_control, :register_controls
         | 
| 568 577 |  | 
| 569 578 |  | 
| 570 579 | 
             
            	### Map the specified LDAP +attribute+ to its Ruby datatype if one is registered for the given 
         | 
| @@ -605,7 +614,7 @@ class Treequel::Directory | |
| 605 614 | 
             
            	### Return an Array of OID strings representing the controls supported by the Directory, 
         | 
| 606 615 | 
             
            	### as listed in the directory's root DSE.
         | 
| 607 616 | 
             
            	def supported_control_oids
         | 
| 608 | 
            -
            		return self. | 
| 617 | 
            +
            		return self.root_dse[:supportedControl]
         | 
| 609 618 | 
             
            	end
         | 
| 610 619 |  | 
| 611 620 |  | 
| @@ -620,7 +629,7 @@ class Treequel::Directory | |
| 620 629 | 
             
            	### Return an Array of OID strings representing the extensions supported by the Directory, 
         | 
| 621 630 | 
             
            	### as listed in the directory's root DSE.
         | 
| 622 631 | 
             
            	def supported_extension_oids
         | 
| 623 | 
            -
            		return self. | 
| 632 | 
            +
            		return self.root_dse[:supportedExtension]
         | 
| 624 633 | 
             
            	end
         | 
| 625 634 |  | 
| 626 635 |  | 
| @@ -635,7 +644,7 @@ class Treequel::Directory | |
| 635 644 | 
             
            	### Return an Array of OID strings representing the features supported by the Directory, 
         | 
| 636 645 | 
             
            	### as listed in the directory's root DSE.
         | 
| 637 646 | 
             
            	def supported_feature_oids
         | 
| 638 | 
            -
            		return self. | 
| 647 | 
            +
            		return self.root_dse[:supportedFeatures]
         | 
| 639 648 | 
             
            	end
         | 
| 640 649 |  | 
| 641 650 |  | 
| @@ -675,9 +684,7 @@ class Treequel::Directory | |
| 675 684 |  | 
| 676 685 | 
             
            	### Fetch the default base dn for the server from the server's Root DSE.
         | 
| 677 686 | 
             
            	def get_default_base_dn
         | 
| 678 | 
            -
            		 | 
| 679 | 
            -
            		return '' if dse.nil? || dse.empty?
         | 
| 680 | 
            -
            		return dse.first['namingContexts'].first
         | 
| 687 | 
            +
            		return self.root_dse[:namingContexts].first.dn
         | 
| 681 688 | 
             
            	end
         | 
| 682 689 |  | 
| 683 690 |  | 
| @@ -694,23 +701,6 @@ class Treequel::Directory | |
| 694 701 | 
             
            	end
         | 
| 695 702 |  | 
| 696 703 |  | 
| 697 | 
            -
            	### Normalize the attributes in +hash+ to be of the form expected by the
         | 
| 698 | 
            -
            	### LDAP library (i.e., keys as Strings, values as Arrays of Strings)
         | 
| 699 | 
            -
            	def normalize_attributes( hash )
         | 
| 700 | 
            -
            		normhash = {}
         | 
| 701 | 
            -
            		hash.each do |key,val|
         | 
| 702 | 
            -
            			val = [ val ] unless val.is_a?( Array )
         | 
| 703 | 
            -
            			val.collect! {|obj| obj.to_s }
         | 
| 704 | 
            -
             | 
| 705 | 
            -
            			normhash[ key.to_s ] = val
         | 
| 706 | 
            -
            		end
         | 
| 707 | 
            -
             | 
| 708 | 
            -
            		normhash.delete( 'dn' )
         | 
| 709 | 
            -
             | 
| 710 | 
            -
            		return normhash
         | 
| 711 | 
            -
            	end
         | 
| 712 | 
            -
             | 
| 713 | 
            -
             | 
| 714 704 | 
             
            	### Normalize the parameters to the #search method into the format expected by 
         | 
| 715 705 | 
             
            	### the LDAP::Conn#Search_ext2 method and return them as a Hash.
         | 
| 716 706 | 
             
            	def normalize_search_parameters( base, scope, filter, parameters )
         |