forked from zammad/zammad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathldap.rb
215 lines (184 loc) · 5.85 KB
/
ldap.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
require_dependency 'net/ldap'
require_dependency 'net/ldap/entry'
# Class for establishing LDAP connections. A wrapper around Net::LDAP needed for Auth and Sync.
# ATTENTION: Loads custom 'net/ldap/entry' from 'lib/core_ext' which extends the Net::LDAP::Entry class.
#
# @!attribute [r] connection
# @return [Net::LDAP] the Net::LDAP instance with the established connection
# @!attribute [r] base_dn
# @return [String] the base dn used while initializing the connection
class Ldap
attr_reader :base_dn, :host, :port, :ssl
# Initializes a LDAP connection.
#
# @param [Hash] config the configuration for establishing a LDAP connection. Default is Setting 'ldap_config'.
# @option config [String] :host_url The LDAP host URL in the format '*protocol*://*host*:*port*'.
# @option config [String] :host The LDAP explicit host. May contain the port. Gets overwritten by host_url if given.
# @option config [Number] :port The LDAP port. Default is 389 LDAP or 636 for LDAPS. Gets overwritten by host_url if given.
# @option config [Boolean] :ssl The LDAP SSL setting. Is set automatically for 'ldaps' protocol. Sets Port to 636 if non other is given.
# @option config [String] :base_dn The base DN searches etc. are applied to.
# @option config [String] :bind_user The username which should be used for bind.
# @option config [String] :bind_pw The password which should be used for bind.
#
# @example
# ldap = Ldap.new
#
# @return [nil]
def initialize(config = nil)
@config = config
if @config.blank?
@config = Setting.get('ldap_config')
end
# connect on initialization
connection
end
# Requests the rootDSE (the root of the directory data tree on a directory server).
#
# @example
# ldap.preferences
# #=> [:namingcontexts=>["DC=domain,DC=tld", "CN=Configuration,DC=domain,DC=tld"], :supportedldapversion=>["3", "2"], ...]
#
# @return [Hash{String => Array<String>}] The found RootDSEs.
def preferences
connection.search_root_dse.to_h
end
# Performs a LDAP search and yields over the found LDAP entries.
#
# @param filter [String] The filter that should get applied to the search.
# @param base [String] The base DN on which the search should get executed. Default is initialization parameter.
# @param scope [Net::LDAP::SearchScope] The search scope as defined in Net::LDAP SearchScopes. Default is WholeSubtree.
# @param attributes [Array<String>] Limits the requested entry attributes to the given list of attributes which increses the performance.
#
# @example
# ldap.search('(objectClass=group)') do |entry|
# p entry
# end
# #=> <Net::LDAP::Entry...>
#
# @return [true] Returns always true
def search(filter, base: nil, scope: nil, attributes: nil)
base ||= base_dn()
scope ||= Net::LDAP::SearchScope_WholeSubtree
connection.search(
base: base,
filter: filter,
scope: scope,
attributes: attributes,
return_result: false, # improves performance
) do |entry|
yield entry
end
end
# Checks if there are any entries for the given search criteria.
#
# @param (see Ldap#search)
#
# @example
# ldap.entries?('(objectClass=group)')
# #=> true
#
# @return [Boolean] Returns true if entries are present false if not.
def entries?(*args)
found = false
search(*args) do |_entry|
found = true
break
end
found
end
# Counts the entries for the given search criteria.
#
# @param (see Ldap#search)
#
# @example
# ldap.entries?('(objectClass=group)')
# #=> 10
#
# @return [Number] The count of matching entries.
def count(*args)
counter = 0
search(*args) do |_entry|
counter += 1
end
counter
end
def connection
@connection ||= begin
attributes_from_config
binded_connection
end
end
private
def binded_connection
# ldap connect
ldap = Net::LDAP.new(connection_params)
# set auth data if needed
if @bind_user && @bind_pw
ldap.auth @bind_user, @bind_pw
end
return ldap if ldap.bind
result = ldap.get_operation_result
raise Exceptions::UnprocessableEntity, "Can't bind to '#{@host}', #{result.code}, #{result.message}"
rescue => e
Rails.logger.error e
raise Exceptions::UnprocessableEntity, "Can't connect to '#{@host}' on port '#{@port}', #{e}"
end
def connection_params
params = {
host: @host,
port: @port,
}
if @encryption
params[:encryption] = @encryption
end
# special workaround for IBM bluepages
# see issue #1422 for more details
if @host == 'bluepages.ibm.com'
params[:force_no_page] = true
end
params
end
def attributes_from_config
# might change below
@host = @config[:host]
@port = @config[:port]
@ssl = @config.fetch(:ssl, false)
parse_host_url
parse_host
handle_ssl_config
handle_bind_crendentials
@base_dn = @config[:base_dn]
# fallback to default
# port if none given
@port ||= 389 # rubocop:disable Naming/MemoizedInstanceVariableName
end
def parse_host_url
@host_url = @config[:host_url]
return if @host_url.blank?
raise "Invalid host url '#{@host_url}'" if @host_url !~ %r{\A([^:]+)://(.+?)/?\z}
@protocol = $1.to_sym
@host = $2
@ssl = @protocol == :ldaps
end
def parse_host
return if @host !~ /\A([^:]+):(.+?)\z/
@host = $1
@port = $2.to_i
end
def handle_ssl_config
return if !@ssl
@port ||= @config.fetch(:port, 636)
@encryption = {
method: :simple_tls,
}
return if @config[:ssl_verify]
@encryption[:tls_options] = {
verify_mode: OpenSSL::SSL::VERIFY_NONE
}
end
def handle_bind_crendentials
@bind_user = @config[:bind_user]
@bind_pw = @config[:bind_pw]
end
end