Class: ActiveLDAP::Base
- Inherits:
-
Object
- Object
- ActiveLDAP::Base
- Defined in:
- lib/activeldap/base.rb
Overview
Base
Base is the primary class which contains all of the core ActiveLDAP functionality. It is meant to only ever be subclassed by extension classes.
Constant Summary collapse
- @@config =
All class-wide variables
nil- @@schema =
Container for current connection settings
nil- @@conn =
LDAP server’s schema
nil- @@reconnect_attempts =
LDAP connection
0
Instance Attribute Summary collapse
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#may ⇒ Object
readonly
Parsed schema structures.
-
#must ⇒ Object
readonly
Parsed schema structures.
Class Method Summary collapse
-
.base ⇒ Object
Make the return value the string that is your LDAP base.
-
.close ⇒ Object
Base.close This method deletes the LDAP connection object.
-
.connect(config = {}) ⇒ Object
Connect and bind to LDAP creating a class variable for use by all ActiveLDAP objects.
-
.connection ⇒ Object
Return the LDAP connection object currently in use.
-
.connection=(conn) ⇒ Object
Set the LDAP connection avoiding Base.connect or multiplexing connections.
-
.create_object(config = {}) ⇒ Object
Driver generator.
-
.dnattr ⇒ Object
Base.dnattr.
-
.do_anonymous_bind(bind_dn) ⇒ Object
Base.do_anonymous_bind.
-
.do_bind ⇒ Object
Wrapper all bind activity.
-
.do_connect ⇒ Object
Performs the actually connection.
-
.do_sasl_bind(bind_dn) ⇒ Object
Base.do_sasl_bind.
-
.do_simple_bind(bind_dn) ⇒ Object
Base.do_simple_bind.
-
.reconnect(force = false) ⇒ Object
Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.
-
.required_classes ⇒ Object
This is optionally set to the array of objectClass names that are minimally required for EVERY object on your LDAP server.
-
.schema ⇒ Object
Return the schema object.
-
.search(config = {}) ⇒ Object
search.
Instance Method Summary collapse
-
#__methods ⇒ Object
Add available attributes to the methods.
-
#attributes ⇒ Object
attributes.
-
#delete ⇒ Object
delete.
-
#dn ⇒ Object
dn.
-
#exists? ⇒ Boolean
exists?.
-
#initialize(val) ⇒ Base
constructor
new.
-
#method_missing(name, *args) ⇒ Object
method_missing.
- #methods ⇒ Object
-
#validate ⇒ Object
validate.
-
#write ⇒ Object
write.
Constructor Details
#initialize(val) ⇒ Base
new
Creates a new instance of Base initializing all class and all initialization. Defines local defaults. See examples If multiple values exist for dnattr, the first one put here will be authoritative TODO: Add # support for relative distinguished names val can be a dn attribute value, a full DN, or a LDAP::Entry. The use with a LDAP::Entry is primarily meant for internal use by find and find_all.
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/activeldap/base.rb', line 523 def initialize(val) @exists = false # Try a default connection if none made explicitly if Base.connection.nil? and @@reconnect_attempts < @@config[:retries] # Use @@config if it has been prepopulated and the conn is down. if @@config ActiveLDAP::Base.reconnect else ActiveLDAP::Base.connect end elsif Base.connection.nil? @@logger.error('Attempted to initialize a new object with no connection') raise ConnectionError, 'Number of reconnect attempts exceeded.' end if val.class == LDAP::Entry # Call import, which is basically initialize # without accessing LDAP. @@logger.debug "initialize: val is a LDAP::Entry - running import." import(val) return end if val.class != String raise TypeError, "Object key must be a String" end @data = {} # where the r/w entry data is stored @ldap_data = {} # original ldap entry data @attr_methods = {} # list of valid method calls for attributes used for dereferencing @last_oc = false # for use in other methods for "caching" if dnattr().empty? raise RuntimeError, "dnattr() not set for this class." end # Break val apart if it is a dn if val.match(/^#{dnattr()}=([^,=]+),#{base()}$/i) val = $1 elsif val.match(/[=,]/) @@logger.info "initialize: Changing val from '#{val}' to '' because it doesn't match the DN." val = '' end # Do a search - if it exists, pull all data and parse schema, if not, just set the hierarchical data if val.class != String or val.empty? raise TypeError, 'a dn attribute String must be supplied ' + 'on initialization' else # Create what should be the authoritative DN @dn = "#{dnattr()}=#{val},#{base()}" # Search for the existing entry begin # Get some attributes Base.connection.search(base(), @@config[:ldap_scope], "(#{dnattr()}=#{val})") do |m| @exists = true # Save DN @dn = m.dn # Load up data into tmp @@logger.debug("loading entry: #{@dn}") m.attrs.each do |attr| # Load with subtypes just like @data @@logger.debug("calling make_subtypes for m.vals(attr).dup") safe_attr, value = make_subtypes(attr, m.vals(attr).dup) @@logger.debug("finished make_subtypes for #{attr}") # Add subtype to any existing values if @ldap_data.has_key? safe_attr value.each do |v| @ldap_data[safe_attr].push(v) end else @ldap_data[safe_attr] = value end end end rescue RuntimeError => detail #TODO# Check for 'No message' when retrying # The connection may have gone stale. Let's reconnect and retry. retry if Base.reconnect() # Do nothing on failure @@logger.error('new: unable to search for entry') raise detail rescue LDAP::ResultError end end # Do the actual object setup work. if @exists # Populate schema data send(:apply_objectclass, @ldap_data['objectClass']) # Populate real data now that we have the schema with aliases @ldap_data.each do |pair| real_attr = @attr_methods[pair[0]] @@logger.debug("new: #{pair[0].inspect} method maps to #{real_attr}") @data[real_attr] = pair[1].dup @@logger.debug("new: #{real_attr} set to #{pair[1]}") end else send(:apply_objectclass, required_classes()) # Setup dn attribute (later rdn too!) real_dnattr = @attr_methods[dnattr()] @data[real_dnattr] = val @@logger.debug("new: setting dnattr: #{real_dnattr} = #{val}") end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
method_missing
If a given method matches an attribute or an attribute alias then call the appropriate method. TODO: Determine if it would be better to define each allowed method
using class_eval instead of using method_missing. This would
give tab completion in irb.
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 |
# File 'lib/activeldap/base.rb', line 900 def method_missing(name, *args) @@logger.debug("stub: called method_missing(#{name.inspect}, #{args.inspect})") # dynamically update the available attributes without requiring an # explicit call. The cache 'last_oc' saves a lot of cpu time. if @data['objectClass'] != @last_oc @@logger.debug("method_missing(#{name.inspect}, #{args.inspect}): updating apply_objectclass(#{@data['objectClass'].inspect})") send(:apply_objectclass, @data['objectClass']) end key = name.to_s case key when /^(\S+)=$/ real_key = $1 @@logger.debug("method_missing: attr_methods has_key? #{real_key}") if @attr_methods.has_key? real_key raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size != 1 @@logger.debug("method_missing: calling :attribute_method=(#{real_key}, #{args[0]})") return send(:attribute_method=, real_key, args[0]) end else @@logger.debug("method_missing: attr_methods has_key? #{key}") if @attr_methods.has_key? key raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size > 1 return attribute_method(key, *args) end end raise NoMethodError, "undefined method `#{key}' for #{self}" end |
Instance Attribute Details
#logger ⇒ Object
Returns the value of attribute logger.
105 106 107 |
# File 'lib/activeldap/base.rb', line 105 def logger @logger end |
#may ⇒ Object (readonly)
Parsed schema structures
104 105 106 |
# File 'lib/activeldap/base.rb', line 104 def may @may end |
#must ⇒ Object (readonly)
Parsed schema structures
104 105 106 |
# File 'lib/activeldap/base.rb', line 104 def must @must end |
Class Method Details
.base ⇒ Object
Make the return value the string that is your LDAP base
482 483 484 |
# File 'lib/activeldap/base.rb', line 482 def Base.base 'dc=example,dc=com' end |
.close ⇒ Object
Base.close This method deletes the LDAP connection object. This does NOT reset any overridden values from a Base.connect call.
236 237 238 239 240 241 242 243 244 245 |
# File 'lib/activeldap/base.rb', line 236 def Base.close begin @@conn.unbind unless @@conn.nil? rescue # Doesn't matter. end @@conn = nil # Make sure it is cleaned up ObjectSpace.garbage_collect end |
.connect(config = {}) ⇒ Object
Connect and bind to LDAP creating a class variable for use by all ActiveLDAP objects.
config
config must be a hash that may contain any of the following fields: :user, :password_block, :logger, :host, :port, :base, :bind_format, :try_sasl, :allow_anonymous :user specifies the username to bind with. :bind_format specifies the string to substitute the username into on bind. e.g. uid=%s,ou=People,dc=dataspill,dc=org. Overrides @@bind_format. :password_block specifies a Proc object that will yield a String to be used as the password when called. :logger specifies a preconfigured Log4r::Logger to be used for all logging :host sets the LDAP server hostname :port sets the LDAP server port :base overwrites Base.base - this affects EVERYTHING :try_sasl indicates that a SASL bind should be attempted when binding to the server (default: false) :allow_anonymous indicates that a true anonymous bind is allowed when trying to bind to the server (default: true) :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite. :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection :method - whether to use :ssl, :tls, or :plain (unencrypted) :retry_wait - seconds to wait before retrying a connection :ldap_scope - dictates how to find objects. ONELEVEL by default to avoid dn_attr collisions across OUs. Think before changing. See lib/configuration.rb for defaults for each option
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/activeldap/base.rb', line 199 def Base.connect(config={}) # Process config # Class options ## These will be replace by configuration.rb defaults if defined @@config = DEFAULT_CONFIG.dup config.keys.each do |key| if key == :base # Scrub before inserting base = config[:base].gsub(/['}{#]/, '') Base.class_eval("def Base.base();'#{base}';end") else @@config[key] = config[key] end end # Assign a easier name for the logger @@logger = @@config[:logger] || nil # Setup default logger to console if @@logger.nil? @@logger = Log4r::Logger.new('activeldap') @@logger.level = Log4r::OFF Log4r::StderrOutputter.new 'console' @@logger.add('console') end # Reset for the new connection @@reconnect_attempts = 0 # Make the connection. do_connect() # Make irb users happy with a 'true' return true end |
.connection ⇒ Object
Return the LDAP connection object currently in use
248 249 250 |
# File 'lib/activeldap/base.rb', line 248 def Base.connection return @@conn end |
.connection=(conn) ⇒ Object
Set the LDAP connection avoiding Base.connect or multiplexing connections
253 254 255 |
# File 'lib/activeldap/base.rb', line 253 def Base.connection=(conn) @@conn = conn end |
.create_object(config = {}) ⇒ Object
Driver generator
TODO add type checking This let’s you call this method to create top-level extension object. This is really just a proof of concept and has not truly useful purpose. example: Base.create_object(:class => “user”, :dnattr => “uid”, :classes => [‘top’])
THIS METHOD IS DANGEROUS. INPUT IS NOT SANITIZED.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/activeldap/base.rb', line 121 def Base.create_object(config={}) # Just upcase the first letter of the new class name str = config[:class] class_name = str[0].chr.upcase + str[1..-1] attr = config[:dnattr] # "uid" prefix = config[:base] # "ou=People" # [ 'top', 'posixAccount' ] classes_array = config[:classes] || [] # [ [ :groups, {:class_name => "Group", :foreign_key => "memberUid"}] ] belongs_to_array = config[:belongs_to] || [] # [ [ :members, {:class_name => "User", :foreign_key => "uid", :local_key => "memberUid"}] ] has_many_array = config[:has_many] || [] raise TypeError, ":objectclasses must be an array" unless classes_array.respond_to? :size raise TypeError, ":belongs_to must be an array" unless belongs_to_array.respond_to? :size raise TypeError, ":has_many must be an array" unless has_many_array.respond_to? :size # Build classes array classes = '[' classes_array.map! {|x| x = "'#{x}'"} classes << classes_array.join(', ') classes << ']' # Build belongs_to belongs_to = [] if belongs_to_array.size > 0 belongs_to_array.each do |bt| line = [ "belongs_to :#{bt[0]}" ] bt[1].keys.each do |key| line << ":#{key} => '#{bt[1][key]}'" end belongs_to << line.join(', ') end end # Build has_many has_many = [] if has_many_array.size > 0 has_many_array.each do |hm| line = [ "has_many :#{hm[0]}" ] hm[1].keys.each do |key| line << ":#{key} => '#{hm[1][key]}'" end has_many << line.join(', ') end end self.class.module_eval <<-"end_eval" class ::#{class_name} < ActiveLDAP::Base ldap_mapping :dnattr => "#{attr}", :prefix => "#{prefix}", :classes => #{classes} #{belongs_to.join("\n")} #{has_many.join("\n")} end end_eval end |
.dnattr ⇒ Object
Base.dnattr
This is a placeholder for the class method that will be overridden on calling ldap_mapping in a subclass. Using a class method allows for clean inheritance from classes that already have a ldap_mapping.
492 493 494 |
# File 'lib/activeldap/base.rb', line 492 def Base.dnattr '' end |
.do_anonymous_bind(bind_dn) ⇒ Object
Base.do_anonymous_bind
Bind to LDAP with the given DN, but with no password. (anonymous!)
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 |
# File 'lib/activeldap/base.rb', line 1206 def Base.do_anonymous_bind(bind_dn) @@logger.info "Attempting anonymous authentication" begin @@conn.bind() return true rescue => e @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}" @@logger.debug "Exception: #{e.exception}" @@logger.debug "Backtrace: #{e.backtrace}" @@logger.warn "Warning: Anonymous authentication failed." @@logger.warn "message: #{e.}" return false end end |
.do_bind ⇒ Object
Wrapper all bind activity
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 |
# File 'lib/activeldap/base.rb', line 1183 def Base.do_bind() bind_dn = @@config[:bind_format] % [@@config[:user]] # 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 @@config[:try_sasl] and do_sasl_bind(bind_dn) @@logger.info('Bound SASL') elsif do_simple_bind(bind_dn) @@logger.info('Bound simple') elsif @@config[:allow_anonymous] and do_anonymous_bind(bind_dn) @@logger.info('Bound simple') else @@logger.error('Failed to bind using any available method') raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0 end return @@conn.bound? end |
.do_connect ⇒ Object
Performs the actually connection. This separate so that it may be called to refresh stale connections.
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 |
# File 'lib/activeldap/base.rb', line 1143 def Base.do_connect() begin case @@config[:method] when :ssl @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], false) when :tls @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], true) when :plain @@conn = LDAP::Conn.new(@@config[:host], @@config[:port]) else raise ConfigurationError,"#{@@config[:method]} is not one of the available connect methods :ssl, :tls, or :plain" end rescue ConfigurationError => e # Pass through raise e rescue => e @@logger.error("Failed to connect using #{@@config[:method]}") raise e end # Enforce LDAPv3 @@conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) # Authenticate do_bind # Retrieve the schema. We need this to automagically determine attributes begin @@schema = @@conn.schema() if @@schema.nil? rescue => e @@logger.error("Failed to retrieve the schema (#{@@config[:method]})") @@logger.error("Schema failure exception: #{e.exception}") @@logger.error("Schema failure backtrace: #{e.backtrace}") raise ConnectionError, "#{e.exception} - LDAP connection failure, or server does not support schema queries." end @@logger.debug "Connected to #{@@config[:host]}:#{@@config[:port]} using #{@@config[:method]}" end |
.do_sasl_bind(bind_dn) ⇒ Object
Base.do_sasl_bind
Bind to LDAP with the given DN using any available SASL methods
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 |
# File 'lib/activeldap/base.rb', line 1272 def Base.do_sasl_bind(bind_dn) # Get all SASL mechanisms mechanisms = @@conn.root_dse[0]['supportedSASLMechanisms'] # Use GSSAPI if available # Currently only GSSAPI is supported with Ruby/LDAP from # http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm # TODO: Investigate further SASL support if mechanisms.respond_to? :member? and mechanisms.member? 'GSSAPI' begin @@conn.sasl_quiet = @@config[:sasl_quiet] if @@config.has_key?(:sasl_quiet) @@conn.sasl_bind(bind_dn, 'GSSAPI') return true rescue @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}" @@logger.warn "Warning: SASL GSSAPI authentication failed." return false end end return false end |
.do_simple_bind(bind_dn) ⇒ Object
Base.do_simple_bind
Bind to LDAP with the given DN and password
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 |
# File 'lib/activeldap/base.rb', line 1224 def Base.do_simple_bind(bind_dn) # Bail if we have no password or password block if not @@config[:password_block].nil? and not @@config[:password].nil? return false end # TODO: Give a warning to reconnect users with password clearing # Get the passphrase for the first time, or anew if we aren't storing password = '' if not @@config[:password].nil? password = @@config[:password] elsif not @@config[:password_block].nil? unless @@config[:password_block].respond_to?(:call) @@logger.error('Skipping simple bind: ' + ':password_block not nil or Proc object. Ignoring.') return false end password = @@config[:password_block].call else @@logger.error('Skipping simple bind: ' + ':password_block and :password options are empty.') return false end begin @@conn.bind(bind_dn, password) rescue => e @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}" # TODO: replace this with LDAP::err2exception() if @@conn.err == LDAP::LDAP_SERVER_DOWN @@logger.error "Warning: " + e. else @@logger.warn "Warning: SIMPLE authentication failed." end return false end # Store the password for quick reference later if @@config[:store_password] @@config[:password] = password elsif @@config[:store_password] == false @@config[:password] = nil end return true end |
.reconnect(force = false) ⇒ Object
Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/activeldap/base.rb', line 259 def Base.reconnect(force=false) not_connected = true while not_connected # Just to be clean, unbind if possible begin @@conn.unbind() if not @@conn.nil? and @@conn.bound? rescue # Ignore complaints. end @@conn = nil if @@config[:retries] == -1 or force == true @@logger.debug('Attempting to reconnect') # Reset the attempts if this was forced. @@reconnect_attempts = 0 if @@reconnect_attempts != 0 begin do_connect() not_connected = false rescue => detail @@logger.error("Reconnect to server failed: #{detail.exception}") @@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}") # Do not loop if forced raise ConnectionError, detail. if force end elsif @@reconnect_attempts < @@config[:retries] @@logger.debug('Attempting to reconnect') @@reconnect_attempts += 1 begin do_connect() not_connected = false # Reset to 0 if a connection was made. @@reconnect_attempts = 0 rescue => detail @@logger.error("Reconnect to server failed: #{detail.exception}") @@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}") end else # Raise a warning raise ConnectionError, 'Giving up trying to reconnect to LDAP server.' end # Sleep before looping sleep @@config[:retry_wait] end return true end |
.required_classes ⇒ Object
This is optionally set to the array of objectClass names that are minimally required for EVERY object on your LDAP server. If you don’t want one, set this to [].
506 507 508 |
# File 'lib/activeldap/base.rb', line 506 def Base.required_classes [] end |
.schema ⇒ Object
Return the schema object
308 309 310 |
# File 'lib/activeldap/base.rb', line 308 def Base.schema @@schema end |
.search(config = {}) ⇒ Object
search
Wraps Ruby/LDAP connection.search to make it easier to search for specific data without cracking open Base.connection
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/activeldap/base.rb', line 316 def Base.search(config={}) unless Base.connection if @@config ActiveLDAP::Base.connect(@@config) else ActiveLDAP::Base.connect end end config[:filter] = 'objectClass=*' unless config.has_key? :filter config[:attrs] = [] unless config.has_key? :attrs config[:scope] = LDAP::LDAP_SCOPE_SUBTREE unless config.has_key? :scope config[:base] = base() unless config.has_key? :base values = [] config[:attrs] = config[:attrs].to_a # just in case begin @@conn.search(config[:base], config[:scope], config[:filter], config[:attrs]) do |m| res = {} res['dn'] = [m.dn.dup] # For consistency with the below m.attrs.each do |attr| if config[:attrs].member? attr or config[:attrs].empty? res[attr] = m.vals(attr).dup end end values.push(res) end rescue RuntimeError => detail #TODO# Check for 'No message' when retrying # The connection may have gone stale. Let's reconnect and retry. retry if Base.reconnect() # Do nothing on failure @@logger.debug "No matches for #{config[:filter]} and attrs #{config[:attrs]}" end return values end |
Instance Method Details
#__methods ⇒ Object
Add available attributes to the methods
930 |
# File 'lib/activeldap/base.rb', line 930 alias_method :__methods, :methods |
#attributes ⇒ Object
attributes
Return attribute methods so that a program can determine available attributes dynamically without schema awareness
637 638 639 640 641 |
# File 'lib/activeldap/base.rb', line 637 def attributes @@logger.debug("stub: attributes called") send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc return @attr_methods.keys end |
#delete ⇒ Object
delete
Delete this entry from LDAP
706 707 708 709 710 711 712 713 714 715 716 717 718 719 |
# File 'lib/activeldap/base.rb', line 706 def delete @@logger.debug("stub: delete called") begin @@conn.delete(@dn) @exists = false rescue RuntimeError => detail #todo# check for 'no message' when retrying # the connection may have gone stale. let's reconnect and retry. retry if Base.reconnect() raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'" rescue LDAP::ResultError => detail raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'" end end |
#dn ⇒ Object
dn
Return the authoritative dn
654 655 656 657 |
# File 'lib/activeldap/base.rb', line 654 def dn @@logger.debug("stub: dn called") return @dn.dup end |
#exists? ⇒ Boolean
exists?
Return whether the entry exists in LDAP or not
646 647 648 649 |
# File 'lib/activeldap/base.rb', line 646 def exists? @@logger.debug("stub: exists? called") return @exists end |
#methods ⇒ Object
931 932 933 |
# File 'lib/activeldap/base.rb', line 931 def methods return __methods + attributes() end |
#validate ⇒ Object
validate
Basic validation:
-
Verify that every ‘MUST’ specified in the schema has a value defined
-
Enforcement of undefined attributes is handled in the objectClass= method
Must call enforce_types() first before enforcement can be guaranteed
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 |
# File 'lib/activeldap/base.rb', line 665 def validate @@logger.debug("stub: validate called") # Clean up attr values, etc send(:enforce_types) # Validate objectclass settings @data['objectClass'].each do |klass| unless klass.class == String raise TypeError, "Value in objectClass array is not a String. (#{klass.class}:#{klass.inspect})" end unless Base.schema.names("objectClasses").member? klass raise ObjectClassError, "objectClass '#{klass}' unknown to LDAP server." end end # make sure this doesn't drop any of the required objectclasses required_classes().each do |oc| unless @data['objectClass'].member? oc.to_s raise ObjectClassError, "'#{oc}' must be a defined objectClass for class '#{self.class}' as set in the ldap_mapping" end end # Make sure all MUST attributes have a value @data['objectClass'].each do |objc| @must.each do |req_attr| deref = @attr_methods[req_attr] # Set default if it wasn't yet set. @data[deref] = [] if @data[deref].nil? # Check for missing requirements. if @data[deref].empty? raise AttributeEmpty, "objectClass '#{objc}' requires attribute '#{Base.schema.attribute_aliases(req_attr).join(', ')}'" end end end @@logger.debug("stub: validate finished") end |
#write ⇒ Object
write
Write and validate this object into LDAP either adding or replacing attributes TODO: Binary data support TODO: Relative DN support
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 |
# File 'lib/activeldap/base.rb', line 728 def write @@logger.debug("stub: write called") # Validate against the objectClass requirements validate # Put all changes into one change entry to ensure # automatic rollback upon failure. entry = [] # Expand subtypes to real ldap_data entries # We can't reuse @ldap_data because an exception would leave # an object in an unknown state @@logger.debug("#write: dup'ing @ldap_data") ldap_data = Marshal.load(Marshal.dump(@ldap_data)) @@logger.debug("#write: dup finished @ldap_data") @@logger.debug("#write: expanding subtypes in @ldap_data") ldap_data.keys.each do |key| ldap_data[key].each do |value| if value.class == Hash suffix, real_value = extract_subtypes(value) if ldap_data.has_key? key + suffix ldap_data[key + suffix].push(real_value) else ldap_data[key + suffix] = real_value end ldap_data[key].delete(value) end end end @@logger.debug("#write: subtypes expanded for @ldap_data") # Expand subtypes to real data entries, but leave @data alone @@logger.debug("#write: dup'ing @data") data = Marshal.load(Marshal.dump(@data)) @@logger.debug("#write: finished dup'ing @data") @@logger.debug("#write: expanding subtypes for @data") data.keys.each do |key| data[key].each do |value| if value.class == Hash suffix, real_value = extract_subtypes(value) if data.has_key? key + suffix data[key + suffix].push(real_value) else data[key + suffix] = real_value end data[key].delete(value) end end end @@logger.debug("#write: subtypes expanded for @data") if @exists # Cycle through all attrs to determine action action = {} replaceable = [] # Now that all the subtypes will be treated as unique attributes # we can see what's changed and add anything that is brand-spankin' # new. @@logger.debug("#write: traversing ldap_data determining replaces and deletes") ldap_data.each do |pair| suffix = '' binary = 0 name, *suffix_a = pair[0].split(/;/) suffix = ';'+ suffix_a.join(';') if suffix_a.size > 0 name = @attr_methods[name] name = pair[0].split(/;/)[0] if name.nil? # for objectClass, or removed vals value = data[name+suffix] # Detect subtypes and account for them binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name replaceable.push(name+suffix) if pair[1] != value # Create mod entries if not value.empty? # Ditched delete then replace because attribs with no equality match rules # will fails @@logger.debug("updating attribute of existing entry: #{name+suffix}: #{value.inspect}") entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value)) else # Since some types do not have equality matching rules, delete doesn't work # Replacing with nothing is equivalent. @@logger.debug("removing attribute from existing entry: #{name+suffix}") entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, [])) end end end @@logger.debug("#write: finished traversing ldap_data") @@logger.debug("#write: traversing data determining adds") data.each do |pair| suffix = '' binary = 0 name, *suffix_a = pair[0].split(/;/) suffix = ';' + suffix_a.join(';') if suffix_a.size > 0 name = @attr_methods[name] name = pair[0].split(/;/)[0] if name.nil? # for obj class or removed vals value = pair[1] if not replaceable.member? name+suffix # Detect subtypes and account for them binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name @@logger.debug("adding attribute to existing entry: #{name+suffix}: #{value.inspect}") # REPLACE will function like ADD, but doesn't hit EQUALITY problems # TODO: Added equality(attr) to Schema2 entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value)) unless value.empty? end end @@logger.debug("#write: traversing data complete") begin @@logger.debug("#write: modifying #{@dn}") @@conn.modify(@dn, entry) @@logger.debug("#write: modify successful") rescue RuntimeError => detail #todo# check for 'no message' when retrying # the connection may have gone stale. let's reconnect and retry. retry if Base.reconnect() raise WriteError, "Could not update LDAP entry: #{detail}" rescue => detail raise WriteError, "Could not update LDAP entry: #{detail}" end else # add everything! @@logger.debug("#write: adding all attribute value pairs") @@logger.debug("#write: adding #{@attr_methods[dnattr()].inspect} = #{data[@attr_methods[dnattr()]].inspect}") entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, @attr_methods[dnattr()], data[@attr_methods[dnattr()]])) @@logger.debug("#write: adding objectClass = #{data[@attr_methods['objectClass']].inspect}") entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, 'objectClass', data[@attr_methods['objectClass']])) data.each do |pair| if pair[1].size > 0 and pair[0] != 'objectClass' and pair[0] != @attr_methods[dnattr()] # Detect subtypes and account for them if Base.schema.binary? pair[0].split(/;/)[0] binary = LDAP::LDAP_MOD_BVALUES else binary = 0 end @@logger.debug("adding attribute to new entry: #{pair[0].inspect}: #{pair[1].inspect}") entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD|binary, pair[0], pair[1])) end end begin @@logger.debug("#write: adding #{@dn}") @@conn.add(@dn, entry) @@logger.debug("#write: add successful") @exists = true rescue RuntimeError => detail # The connection may have gone stale. Let's reconnect and retry. retry if Base.reconnect() raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}" rescue LDAP::ResultError => detail raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}" end end @@logger.debug("#write: resetting @ldap_data to a dup of @data") @ldap_data = Marshal.load(Marshal.dump(@data)) @@logger.debug("#write: @ldap_data reset complete") @@logger.debug("stub: write exitted") end |