ldaptic 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +41 -0
- data/lib/ldaptic.rb +151 -0
- data/lib/ldaptic/active_model.rb +37 -0
- data/lib/ldaptic/adapters.rb +90 -0
- data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
- data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
- data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
- data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
- data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
- data/lib/ldaptic/attribute_set.rb +283 -0
- data/lib/ldaptic/dn.rb +365 -0
- data/lib/ldaptic/entry.rb +646 -0
- data/lib/ldaptic/error_set.rb +34 -0
- data/lib/ldaptic/errors.rb +136 -0
- data/lib/ldaptic/escape.rb +110 -0
- data/lib/ldaptic/filter.rb +282 -0
- data/lib/ldaptic/methods.rb +387 -0
- data/lib/ldaptic/railtie.rb +9 -0
- data/lib/ldaptic/schema.rb +246 -0
- data/lib/ldaptic/syntaxes.rb +319 -0
- data/test/core.schema +582 -0
- data/test/ldaptic_active_model_test.rb +40 -0
- data/test/ldaptic_adapters_test.rb +35 -0
- data/test/ldaptic_attribute_set_test.rb +57 -0
- data/test/ldaptic_dn_test.rb +110 -0
- data/test/ldaptic_entry_test.rb +22 -0
- data/test/ldaptic_errors_test.rb +23 -0
- data/test/ldaptic_escape_test.rb +47 -0
- data/test/ldaptic_filter_test.rb +53 -0
- data/test/ldaptic_hierarchy_test.rb +90 -0
- data/test/ldaptic_schema_test.rb +44 -0
- data/test/ldaptic_syntaxes_test.rb +66 -0
- data/test/mock_adapter.rb +47 -0
- data/test/rbslapd1.rb +111 -0
- data/test/rbslapd4.rb +172 -0
- data/test/test_helper.rb +2 -0
- metadata +146 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Tim Pope
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
= Ldaptic
|
2
|
+
|
3
|
+
This is an object-oriented LDAP wrapper library I started back in 2007 but
|
4
|
+
only recently polished up and released. It's unique in that it creates a
|
5
|
+
class hierarchy (in a namespace your provide) that exactly mirrors the class
|
6
|
+
hierarchy on the server. For example, on a typical server, you'll get an
|
7
|
+
+InetOrgPerson+ class which inherits from +OrganizationalPerson+ which
|
8
|
+
inherits from +Person+ which inherits from +Top+. You can reopen any of these
|
9
|
+
classes to add additional client side behavior.
|
10
|
+
|
11
|
+
Ldaptic started as mainly a tool to interact with my company's Active
|
12
|
+
Directory server, and I lost interest in it when I left that job. Recently,
|
13
|
+
I've become motivated to work on it again, as some of the blocking issues I
|
14
|
+
faced are now potentially solvable with Active Model.
|
15
|
+
|
16
|
+
== Getting Started
|
17
|
+
|
18
|
+
You need to have either the ruby-ldap or net-ldap gem installed. The former
|
19
|
+
is preferred because it's faster native C. Ldaptic is configured by including
|
20
|
+
a dynamically created module into a namespace of your choosing.
|
21
|
+
|
22
|
+
module Example
|
23
|
+
include Ldaptic::Module(
|
24
|
+
:adapter => :ldap_conn,
|
25
|
+
:base => 'ou=Users,dc=example,dc=com',
|
26
|
+
:host => 'example.com',
|
27
|
+
:username => 'cn=admin,ou=Users,dc=example,dc=com',
|
28
|
+
:password => 'password'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
The adapter field can usually be omitted as it defaults to :ldap_conn or
|
33
|
+
:net_ldap, based on which of the above two gems can be found (though you might
|
34
|
+
want to use the :active_directory adapter, which depends on ruby-ldap,
|
35
|
+
instead). If the base is omitted, it will use the first naming context on the
|
36
|
+
server (usually what you want).
|
37
|
+
|
38
|
+
Entries are retrieved using the search method. Named parameters include
|
39
|
+
:base, :filter, :sort, :limit, :scope, and :attributes. All are optional.
|
40
|
+
|
41
|
+
entries = Example.search(
|
42
|
+
:filter => {:objectClass => 'inetOrgPerson'}
|
43
|
+
:sort => :cn,
|
44
|
+
:limit => 10
|
45
|
+
)
|
46
|
+
|
47
|
+
A Ruby class is created for each objectClass defined on the server. Entries
|
48
|
+
are instances of these classes.
|
49
|
+
|
50
|
+
>> entry = Example.find('cn=admin,ou=Users,dc=example,dc=com')
|
51
|
+
=> #<Example::InetOrgPerson cn=admin,ou=Users,dc=example,dc=com ...>
|
52
|
+
>> entry.class.superclass
|
53
|
+
=> Example::OrganizationalPerson
|
54
|
+
|
55
|
+
Predictably, entries have attribute readers and writers.
|
56
|
+
|
57
|
+
>> entry.cn
|
58
|
+
=> <["admin"]>
|
59
|
+
>> entry.cn = "root"
|
60
|
+
>> entry[:cn]
|
61
|
+
=> <["root"]>
|
62
|
+
>> entry[:cn] = "admin"
|
63
|
+
|
64
|
+
The returned object is an attribute set and behaves similar to an array. Some
|
65
|
+
attributes are marked by the server as "single value;" those will return the
|
66
|
+
first element on method access but an attribute set on indexing access, for
|
67
|
+
programmatic convenience.
|
68
|
+
|
69
|
+
>> entry.uidNumber
|
70
|
+
=> 0
|
71
|
+
>> entry[:uidNumber]
|
72
|
+
=> <[0]>
|
73
|
+
|
74
|
+
The indexing syntax can also be used to create and fetch children.
|
75
|
+
|
76
|
+
>> users
|
77
|
+
=> #<Example::OrganizationalUnit ou=Users,dc=example,dc=com ...>
|
78
|
+
>> users[:cn=>'admin'] = Example::InetOrgPerson.new
|
79
|
+
=> #<Example::InetOrgPerson cn=admin,ou=Users,dc=example,dc=com ...>
|
80
|
+
>> users[:cn=>'admin']
|
81
|
+
=> #<Example::InetOrgPerson cn=admin,ou=Users,dc=example,dc=com ...>
|
82
|
+
|
83
|
+
Entries also implement many of the standard methods you've come to expect in
|
84
|
+
an Active Record world (save, valid?, errors, to_param, attributes, ...).
|
85
|
+
In fact, it is fully Active Model compliant.
|
86
|
+
|
87
|
+
For more information, see in particular Ldaptic::Methods (for namespace
|
88
|
+
methods like search), Ldaptic::Entry, and Ldaptic::AttributeSet.
|
89
|
+
|
90
|
+
== To Do
|
91
|
+
|
92
|
+
* Verify everything still works. The tests take care of much of this, but
|
93
|
+
there are no integration tests for the adapters. In particular, I have no
|
94
|
+
way to verify the Active Directory adapter still works.
|
95
|
+
|
96
|
+
* The test suite (reflecting my fledgling testing abilities from 2007) is more
|
97
|
+
smoke test than BDD. Perhaps switch to RSpec in the quest to rectify this.
|
98
|
+
|
99
|
+
* Potential new features (mostly along the lines of "make it more like Active
|
100
|
+
Record") are in the GitHub issue tracker. Vote for and comment on the ones
|
101
|
+
you would find useful, as most are on hold until someone has a real use
|
102
|
+
case.
|
103
|
+
|
104
|
+
* Pick a better name? Ldaptic has an ambiguous spelling.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rake/packagetask'
|
8
|
+
require 'rake/gempackagetask'
|
9
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
10
|
+
|
11
|
+
task :default => :test
|
12
|
+
|
13
|
+
Rake::TestTask.new do |t|
|
14
|
+
t.libs << "test"
|
15
|
+
t.test_files = Dir['test/*_test.rb']
|
16
|
+
t.verbose = true
|
17
|
+
end
|
18
|
+
|
19
|
+
Rake::RDocTask.new do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'doc'
|
21
|
+
rdoc.rdoc_files.add('README.rdoc', 'lib')
|
22
|
+
rdoc.main = 'README.rdoc'
|
23
|
+
rdoc.title = 'Ldaptic'
|
24
|
+
rdoc.options << '--inline-source'
|
25
|
+
rdoc.options << '-d' if `which dot` =~ /\/dot/
|
26
|
+
end
|
27
|
+
|
28
|
+
spec = eval(File.read(File.join(File.dirname(__FILE__), 'ldaptic.gemspec')))
|
29
|
+
Rake::GemPackageTask.new(spec) do |p|
|
30
|
+
p.gem_spec = spec
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |t|
|
36
|
+
t.test_files = Dir['test/*_test.rb']
|
37
|
+
t.verbose = true
|
38
|
+
t.rcov_opts << "--exclude '/(rcov|net-ldap|i18n|active(?:support|model))-'"
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
end
|
data/lib/ldaptic.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ldaptic/dn'
|
4
|
+
require 'ldaptic/filter'
|
5
|
+
require 'ldaptic/errors'
|
6
|
+
require 'ldaptic/schema'
|
7
|
+
require 'ldaptic/syntaxes'
|
8
|
+
require 'ldaptic/adapters'
|
9
|
+
require 'ldaptic/entry'
|
10
|
+
require 'ldaptic/methods'
|
11
|
+
|
12
|
+
# = Getting started
|
13
|
+
#
|
14
|
+
# See the methods of the Ldaptic module (below) for information on connecting.
|
15
|
+
#
|
16
|
+
# See the Ldaptic::Methods module for information on searching with your
|
17
|
+
# connection object.
|
18
|
+
#
|
19
|
+
# Search results are Ldaptic::Entry objects. See the documentation for this
|
20
|
+
# class for information on manipulating and updating them, as well as creating
|
21
|
+
# new entries.
|
22
|
+
module Ldaptic
|
23
|
+
|
24
|
+
SCOPES = {
|
25
|
+
:base => 0, # ::LDAP::LDAP_SCOPE_BASE,
|
26
|
+
:onelevel => 1, # ::LDAP::LDAP_SCOPE_ONELEVEL,
|
27
|
+
:subtree => 2 # ::LDAP::LDAP_SCOPE_SUBTREE
|
28
|
+
}
|
29
|
+
|
30
|
+
# Default logger. If none given, creates a new logger on $stderr.
|
31
|
+
def self.logger
|
32
|
+
unless @logger
|
33
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
34
|
+
return Rails.logger
|
35
|
+
else
|
36
|
+
require 'logger'
|
37
|
+
@logger = Logger.new($stderr)
|
38
|
+
@logger.level = Logger::WARN
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@logger
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.logger=(logger)
|
45
|
+
@logger = logger
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns an object that can be assigned directly to a variable. This allows
|
49
|
+
# for an "anonymous" Ldaptic object.
|
50
|
+
# @my_company = Ldaptic::Object(options)
|
51
|
+
# @my_company::User.class_eval do
|
52
|
+
# alias login sAMAccountName
|
53
|
+
# end
|
54
|
+
def self.Object(options={}, &block)
|
55
|
+
base = ::Module.new do
|
56
|
+
include Ldaptic::Module(options)
|
57
|
+
end
|
58
|
+
if block_given?
|
59
|
+
base.class_eval(&block)
|
60
|
+
end
|
61
|
+
base
|
62
|
+
end
|
63
|
+
|
64
|
+
# Similar to Ldaptic::Class, accepting the same options. Instead of
|
65
|
+
# returning an anonymous class that activates upon inheritance, it returns an
|
66
|
+
# anonymous module that activates upon inclusion.
|
67
|
+
# module MyCompany
|
68
|
+
# include Ldaptic::Module(options)
|
69
|
+
# # This class and many others are created automatically based on
|
70
|
+
# # information from the server.
|
71
|
+
# class User
|
72
|
+
# alias login sAMAccountName
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# me = MyCompany.search(:filter => {:cn => "Name, My"}).first
|
77
|
+
# puts me.login
|
78
|
+
def self.Module(options={})
|
79
|
+
Ldaptic::Module.new(options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# The core constructor of Ldaptic. This method returns an anonymous class
|
83
|
+
# which can then be inherited from.
|
84
|
+
#
|
85
|
+
# The new class is not intended to be instantiated, instead serving as a
|
86
|
+
# namespace. Included in this namespace is a set of class methods, as found
|
87
|
+
# in Ldaptic::Methods, and a class hierarchy mirroring the object classes
|
88
|
+
# found on the server.
|
89
|
+
#
|
90
|
+
# options = {
|
91
|
+
# :adapter => :active_directory,
|
92
|
+
# :host => "pdc.mycompany.com",
|
93
|
+
# :username => "mylogin@mycompany.com",
|
94
|
+
# :password => "mypassword"
|
95
|
+
# }
|
96
|
+
#
|
97
|
+
# class MyCompany < Ldaptic::Class(options)
|
98
|
+
# # This class and many others are created automatically based on
|
99
|
+
# # information from the server.
|
100
|
+
# class User
|
101
|
+
# alias login sAMAccountName
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# me = MyCompany.search(:filter => {:cn => "Name, My"}).first
|
106
|
+
# puts me.login
|
107
|
+
#
|
108
|
+
# Options given to this method are relayed to Ldaptic::Adapters.for. The
|
109
|
+
# documentation for this method should be consulted for further information.
|
110
|
+
def self.Class(options={})
|
111
|
+
klass = ::Class.new(Class)
|
112
|
+
klass.instance_variable_set(:@options, Ldaptic::Adapters.for(options))
|
113
|
+
klass
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
alias Namespace Class
|
118
|
+
end
|
119
|
+
|
120
|
+
# An instance of this subclass of ::Module is returned by the Ldaptic::Module
|
121
|
+
# method.
|
122
|
+
class Module < ::Module #:nodoc:
|
123
|
+
def initialize(options={})
|
124
|
+
super()
|
125
|
+
@options = options
|
126
|
+
end
|
127
|
+
def append_features(base)
|
128
|
+
base.extend(Methods)
|
129
|
+
base.instance_variable_set(:@adapter, Ldaptic::Adapters.for(@options))
|
130
|
+
base.module_eval { build_hierarchy }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# The anonymous class returned by the Ldaptic::Class method descends from
|
135
|
+
# this class.
|
136
|
+
class Class #:nodoc:
|
137
|
+
class << self
|
138
|
+
# Callback which triggers the magic.
|
139
|
+
def inherited(subclass)
|
140
|
+
if options = @options
|
141
|
+
subclass.class_eval { include Ldaptic::Module.new(options) }
|
142
|
+
else
|
143
|
+
subclass.instance_variable_set(:@adapter, @adapter)
|
144
|
+
end
|
145
|
+
super
|
146
|
+
end
|
147
|
+
private :inherited, :new
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'ldaptic'
|
3
|
+
|
4
|
+
ActiveModel::EachValidator.class_eval do
|
5
|
+
def validate(record)
|
6
|
+
attributes.each do |attribute|
|
7
|
+
values = record.read_attribute_for_validation(attribute)
|
8
|
+
values = [values] unless values.respond_to?(:before_type_cast)
|
9
|
+
values.each do |value|
|
10
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
11
|
+
validate_each(record, attribute, value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Ldaptic::Entry
|
18
|
+
include ActiveModel::Validations
|
19
|
+
include ActiveModel::Serializers::Xml
|
20
|
+
include ActiveModel::Serializers::JSON
|
21
|
+
include ActiveModel::Dirty
|
22
|
+
include ActiveModel::Callbacks
|
23
|
+
|
24
|
+
def read_attribute_for_validation(attribute)
|
25
|
+
read_attribute(attribute.to_sym, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
# define_model_callbacks(:save, :destroy)
|
29
|
+
|
30
|
+
validate do
|
31
|
+
@attributes.keys.each do |key|
|
32
|
+
self[key].errors.each do |error|
|
33
|
+
errors.add(key, error)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end if respond_to?(:validate)
|
37
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Ldaptic
|
2
|
+
# RFC1823 - The LDAP Application Program Interface
|
3
|
+
module Adapters
|
4
|
+
|
5
|
+
@adapters ||= {}
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
|
9
|
+
# Internally used by adapters to make themselves available.
|
10
|
+
def register(name, mod) #:nodoc:
|
11
|
+
@adapters[name.to_sym] = mod
|
12
|
+
@adapters
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a new adapter for a given set of options. This method is not
|
16
|
+
# for end user use but is instead called by the Ldaptic::Class,
|
17
|
+
# Ldaptic::Module, and Ldaptic::Object methods.
|
18
|
+
#
|
19
|
+
# The <tt>:adapter</tt> key of the +options+ hash selects which adapter
|
20
|
+
# to use. The following adapters are included with Ldaptic.
|
21
|
+
#
|
22
|
+
# * <tt>:ldap_conn</tt>: a Ruby/LDAP LDAP::Conn connection.
|
23
|
+
# * <tt>:ldap_sslconn</tt>: a Ruby/LDAP LDAP::SSLConn connection.
|
24
|
+
# * <tt>:active_directory</tt>: A wrapper around Ruby/LDAP which takes
|
25
|
+
# into account some of the idiosyncrasies of Active Directory.
|
26
|
+
# * <tt>:net_ldap</tt>: a net-ldap Net::LDAP connection.
|
27
|
+
#
|
28
|
+
# All other options given are passed directly to the adapter. While
|
29
|
+
# different adapters support different options, the following are
|
30
|
+
# typically supported.
|
31
|
+
#
|
32
|
+
# * <tt>:host</tt>: The host to connect to. The default is localhost.
|
33
|
+
# * <tt>:port</tt>: The TCP port to use. Default is provided by the
|
34
|
+
# underlying connection.
|
35
|
+
# * <tt>:username</tt>: The DN to bind with. If not given, an anonymous
|
36
|
+
# bind is used.
|
37
|
+
# * <tt>:password</tt>: Password for binding.
|
38
|
+
# * <tt>:base</tt>: The default base DN. Derived from the server by
|
39
|
+
# default.
|
40
|
+
def for(options)
|
41
|
+
require 'ldaptic/adapters/abstract_adapter'
|
42
|
+
# Allow an adapter to be passed directly in for backwards compatibility.
|
43
|
+
if defined?(::LDAP::Conn) && options.kind_of?(::LDAP::Conn)
|
44
|
+
options = {:adapter => :ldap_conn, :connection => options}
|
45
|
+
elsif defined?(::Net::LDAP) && options.kind_of?(::Net::LDAP)
|
46
|
+
options = {:adapter => :net_ldap, :connection => options}
|
47
|
+
end
|
48
|
+
if options.kind_of?(Hash)
|
49
|
+
options = options.inject({}) {|h,(k,v)| h[k.to_sym] = v; h}
|
50
|
+
if options.has_key?(:connection) && !options.has_key?(:adapter)
|
51
|
+
options[:adapter] = options[:connection].class.name.downcase.gsub('::','_')
|
52
|
+
end
|
53
|
+
options[:adapter] ||= default_adapter
|
54
|
+
unless options[:adapter]
|
55
|
+
Ldaptic::Errors.raise(ArgumentError.new("No adapter specfied"))
|
56
|
+
end
|
57
|
+
begin
|
58
|
+
require "ldaptic/adapters/#{options[:adapter]}_adapter"
|
59
|
+
rescue LoadError
|
60
|
+
end
|
61
|
+
if adapter = @adapters[options[:adapter].to_sym]
|
62
|
+
adapter.new(options)
|
63
|
+
else
|
64
|
+
Ldaptic::Errors.raise(ArgumentError.new("Adapter #{options[:adapter]} not found"))
|
65
|
+
end
|
66
|
+
else
|
67
|
+
if options.kind_of?(::Ldaptic::Adapters::AbstractAdapter)
|
68
|
+
options
|
69
|
+
else
|
70
|
+
Ldaptic::Errors.raise(TypeError.new("#{options.class} is not a valid connection type"))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def default_adapter
|
77
|
+
require 'ldap'
|
78
|
+
:ldap_conn
|
79
|
+
rescue LoadError
|
80
|
+
begin
|
81
|
+
require 'net/ldap'
|
82
|
+
:net_ldap
|
83
|
+
rescue LoadError
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|