15 |
15 |
# along with this program; if not, write to the Free Software
|
16 |
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17 |
17 |
|
18 |
|
require 'net/ldap'
|
|
18 |
require 'ldap'
|
19 |
19 |
require 'iconv'
|
20 |
20 |
|
|
21 |
# Contributed by mathew <meta@pobox.com>
|
|
22 |
# Alternative AuthSourceLdap which uses Ruby's interface to native OpenLDAP.
|
|
23 |
# I found this more reliable than net/ldap (pure Ruby LDAP).
|
|
24 |
# OpenLDAP doesn't appear to support authentication in order to connect to
|
|
25 |
# the LDAP server, or if it does I can't find any documentation about it.
|
|
26 |
# Since the LDAP server I care about doesn't require auth, that's fine with me;
|
|
27 |
# I just note it here as an FYI.
|
|
28 |
# There's also a start_tls parameter to LDAP::SSLConn which I don't know what
|
|
29 |
# to do with. For me, both options fail, so I tunnel my LDAP instead of
|
|
30 |
# using SSLConn.
|
21 |
31 |
class AuthSourceLdap < AuthSource
|
22 |
32 |
validates_presence_of :host, :port, :attr_login
|
23 |
33 |
validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
|
24 |
34 |
validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
|
25 |
35 |
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
|
26 |
36 |
validates_numericality_of :port, :only_integer => true
|
27 |
|
|
|
37 |
|
28 |
38 |
before_validation :strip_ldap_attributes
|
29 |
39 |
|
30 |
40 |
def after_initialize
|
... | ... | |
32 |
42 |
end
|
33 |
43 |
|
34 |
44 |
def authenticate(login, password)
|
|
45 |
login.downcase!
|
|
46 |
logger.debug "replacement LDAP auth called" if logger && logger.debug?
|
35 |
47 |
return nil if login.blank? || password.blank?
|
36 |
48 |
attrs = []
|
37 |
|
# get user's DN
|
38 |
|
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
39 |
|
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
|
40 |
|
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
|
|
49 |
# Get user's DN
|
|
50 |
ldap_con = initialize_ldap_con()
|
|
51 |
filter = "(&(objectClass=person)(#{self.attr_login}=#{login}))"
|
|
52 |
logger.debug "filter = #{filter}" if logger && logger.debug?
|
|
53 |
logger.debug "base dn = #{self.base_dn}" if logger && logger.debug?
|
41 |
54 |
dn = String.new
|
42 |
|
ldap_con.search( :base => self.base_dn,
|
43 |
|
:filter => object_filter & login_filter,
|
44 |
|
# only ask for the DN if on-the-fly registration is disabled
|
45 |
|
:attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry|
|
46 |
|
dn = entry.dn
|
|
55 |
# ruby-ldap appears to give you the dn whether you ask for it or not,
|
|
56 |
# but via a special get_dn method rather than as an attribute.
|
|
57 |
attrlist = [self.attr_firstname,self.attr_lastname,self.attr_mail]
|
|
58 |
logger.debug "SEARCH" if logger && logger.debug?
|
|
59 |
ldap_con.search(self.base_dn, LDAP::LDAP_SCOPE_SUBTREE, filter, attrlist) do |entry|
|
|
60 |
logger.debug "search_ext returned >= 1 result" if logger && logger.debug?
|
|
61 |
dn = entry.get_dn
|
47 |
62 |
attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
|
48 |
63 |
:lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
|
49 |
64 |
:mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
|
50 |
65 |
:auth_source_id => self.id ] if onthefly_register?
|
51 |
66 |
end
|
|
67 |
logger.debug "Passed search_ext block" if logger && logger.debug?
|
52 |
68 |
return nil if dn.empty?
|
53 |
69 |
logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
|
54 |
|
# authenticate user
|
55 |
|
ldap_con = initialize_ldap_con(dn, password)
|
56 |
|
return nil unless ldap_con.bind
|
57 |
|
# return user's attributes
|
|
70 |
# Found user, so try to authenticate
|
|
71 |
ldap_con.unbind
|
|
72 |
if ldap_con.bind(dn,password)
|
|
73 |
# Return user's attributes
|
58 |
74 |
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
|
59 |
|
attrs
|
60 |
|
rescue Net::LDAP::LdapError => text
|
61 |
|
raise "LdapError: " + text
|
|
75 |
return attrs
|
|
76 |
end
|
|
77 |
rescue LDAP::ResultError => text
|
|
78 |
logger.info "Authentication failure: #{text}" if logger && logger.info?
|
|
79 |
return nil
|
62 |
80 |
end
|
63 |
81 |
|
64 |
82 |
# test the connection to the LDAP
|
65 |
83 |
def test_connection
|
66 |
|
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
67 |
|
ldap_con.open { }
|
68 |
|
rescue Net::LDAP::LdapError => text
|
|
84 |
ldap_con = initialize_ldap_con
|
|
85 |
rescue Net::LDAP::LdapError => text
|
69 |
86 |
raise "LdapError: " + text
|
70 |
87 |
end
|
71 |
88 |
|
... | ... | |
73 |
90 |
"LDAP"
|
74 |
91 |
end
|
75 |
92 |
|
76 |
|
private
|
77 |
|
|
|
93 |
private
|
78 |
94 |
def strip_ldap_attributes
|
79 |
95 |
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
|
80 |
96 |
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
|
81 |
97 |
end
|
82 |
98 |
end
|
83 |
|
|
84 |
|
def initialize_ldap_con(ldap_user, ldap_password)
|
85 |
|
options = { :host => self.host,
|
86 |
|
:port => self.port,
|
87 |
|
:encryption => (self.tls ? :simple_tls : nil)
|
88 |
|
}
|
89 |
|
options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
|
90 |
|
Net::LDAP.new options
|
|
99 |
|
|
100 |
def initialize_ldap_con
|
|
101 |
logger.debug "Setting up LDAP connection to #{self.host}:#{self.port}"
|
|
102 |
if self.tls
|
|
103 |
# Last parameter determines if START_TLS is used for the SSL connection.
|
|
104 |
return LDAP::SSLConn.new(self.host, self.port, true)
|
|
105 |
else
|
|
106 |
return LDAP::Conn.new(self.host, self.port)
|
|
107 |
end
|
91 |
108 |
end
|
92 |
109 |
|
93 |
110 |
def self.get_attr(entry, attr_name)
|