Project

General

Profile

Defect #1420 » auth_source_ldap.rb

Replacement LDAP authentication code using Ruby's OpenLDAP interface - mathew murphy, 2008-06-10 19:18

 
1
# redMine - project management software
2
# Copyright (C) 2006  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

    
18
require 'ldap'
19
require 'iconv'
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.
31
class AuthSourceLdap < AuthSource 
32
  validates_presence_of :host, :port, :attr_login
33
  validates_presence_of :attr_firstname, :attr_lastname, :attr_mail, :if => Proc.new { |a| a.onthefly_register? }
34
  
35
  def after_initialize
36
    self.port = 389 if self.port == 0
37
  end
38
  
39
  def authenticate(login, password)
40
    login.downcase!
41
    logger.debug "replacement LDAP auth called" if logger && logger.debug?
42
    return nil if login.blank? || password.blank?
43
    attrs = []
44
    # Get user's DN
45
    ldap_con = initialize_ldap_con()
46
    filter = "(&(objectClass=person)(#{self.attr_login}=#{login}))"
47
    logger.debug "filter = #{filter}" if logger && logger.debug?
48
    logger.debug "base dn = #{self.base_dn}" if logger && logger.debug?
49
    dn = String.new
50
    # ruby-ldap appears to give you the dn whether you ask for it or not,
51
    # but via a special get_dn method rather than as an attribute.
52
    attrlist = [self.attr_firstname,self.attr_lastname,self.attr_mail]
53
    logger.debug "SEARCH" if logger && logger.debug?
54
    ldap_con.search(self.base_dn, LDAP::LDAP_SCOPE_SUBTREE, filter, attrlist) do |entry|
55
      logger.debug "search_ext returned >= 1 result" if logger && logger.debug?
56
      dn = entry.get_dn
57
      attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
58
               :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
59
               :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
60
               :auth_source_id => self.id ] if onthefly_register?
61
    end
62
    logger.debug "Passed search_ext block" if logger && logger.debug?
63
    return nil if dn.empty?
64
    logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
65
    # Found user, so try to authenticate
66
    ldap_con.unbind
67
    if ldap_con.bind(dn,password)
68
    # Return user's attributes
69
    logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
70
      return attrs    
71
    end
72
  rescue LDAP::ResultError => text
73
    logger.info "Authentication failure: #{text}" if logger && logger.info?
74
    return nil
75
  end
76

    
77
  # test the connection to the LDAP
78
  def test_connection
79
    ldap_con = initialize_ldap_con
80
  rescue Net::LDAP::LdapError => text
81
    raise "LdapError: " + text
82
  end
83
 
84
  def auth_method_name
85
    "LDAP"
86
  end
87
  
88
private
89
  def initialize_ldap_con
90
    logger.debug "Setting up LDAP connection to #{self.host}:#{self.port}"
91
    if self.tls
92
      # Last parameter determines if START_TLS is used for the SSL connection.
93
      return LDAP::SSLConn.new(self.host, self.port, true)
94
    else
95
      return LDAP::Conn.new(self.host, self.port)
96
    end
97
  end
98
  
99
  def self.get_attr(entry, attr_name)
100
    entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
101
  end
102
end
(1-1/2)