ruby-net-ldap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,108 @@
1
+ # $Id: dataset.rb 78 2006-04-26 02:57:34Z blackhedd $
2
+ #
3
+ #
4
+ #----------------------------------------------------------------------------
5
+ #
6
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
7
+ #
8
+ # Gmail: garbagecat10
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
+ #
24
+ #---------------------------------------------------------------------------
25
+ #
26
+ #
27
+
28
+
29
+
30
+
31
+ module Net
32
+ class LDAP
33
+
34
+ class Dataset < Hash
35
+
36
+ attr_reader :comments
37
+
38
+
39
+ def Dataset::read_ldif io
40
+ ds = Dataset.new
41
+
42
+ line = io.gets && chomp
43
+ dn = nil
44
+
45
+ while line
46
+ io.gets and chomp
47
+ if $_ =~ /^[\s]+/
48
+ line << " " << $'
49
+ else
50
+ nextline = $_
51
+
52
+ if line =~ /^\#/
53
+ ds.comments << line
54
+ elsif line =~ /^dn:[\s]*/i
55
+ dn = $'
56
+ ds[dn] = Hash.new {|k,v| k[v] = []}
57
+ elsif line.length == 0
58
+ dn = nil
59
+ elsif line =~ /^([^:]+):([\:]?)[\s]*/
60
+ # $1 is the attribute name
61
+ # $2 is a colon iff the attr-value is base-64 encoded
62
+ # $' is the attr-value
63
+ # Avoid the Base64 class because not all Ruby versions have it.
64
+ attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
65
+ ds[dn][$1.downcase.intern] << attrvalue
66
+ end
67
+
68
+ line = nextline
69
+ end
70
+ end
71
+
72
+ ds
73
+ end
74
+
75
+
76
+ def initialize
77
+ @comments = []
78
+ end
79
+
80
+
81
+ def to_ldif
82
+ ary = []
83
+ ary += (@comments || [])
84
+
85
+ keys.sort.each {|dn|
86
+ ary << "dn: #{dn}"
87
+
88
+ self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr|
89
+ self[dn][attr.intern].each {|val|
90
+ ary << "#{attr}: #{val}"
91
+ }
92
+ }
93
+
94
+ ary << ""
95
+ }
96
+
97
+ block_given? and ary.each {|line| yield line}
98
+
99
+ ary
100
+ end
101
+
102
+
103
+ end # Dataset
104
+
105
+ end # LDAP
106
+ end # Net
107
+
108
+
@@ -0,0 +1,82 @@
1
+ # $Id: entry.rb 95 2006-05-01 12:19:16Z blackhedd $
2
+ #
3
+ # LDAP Entry (search-result) support classes
4
+ #
5
+ #
6
+ #----------------------------------------------------------------------------
7
+ #
8
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
9
+ #
10
+ # Gmail: garbagecat10
11
+ #
12
+ # This program is free software; you can redistribute it and/or modify
13
+ # it under the terms of the GNU General Public License as published by
14
+ # the Free Software Foundation; either version 2 of the License, or
15
+ # (at your option) any later version.
16
+ #
17
+ # This program is distributed in the hope that it will be useful,
18
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ # GNU General Public License for more details.
21
+ #
22
+ # You should have received a copy of the GNU General Public License
23
+ # along with this program; if not, write to the Free Software
24
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25
+ #
26
+ #---------------------------------------------------------------------------
27
+ #
28
+
29
+
30
+
31
+
32
+ module Net
33
+ class LDAP
34
+
35
+
36
+ class Entry
37
+
38
+ def initialize dn = nil
39
+ @myhash = Hash.new {|k,v| k[v] = [] }
40
+ @myhash[:dn] = [dn]
41
+ end
42
+
43
+
44
+ def []= name, value
45
+ sym = name.to_s.downcase.intern
46
+ @myhash[sym] = value
47
+ end
48
+
49
+
50
+ #--
51
+ # We have to deal with this one as we do []=
52
+ # because this one and not the other one gets called
53
+ # in formulations like entry["CN"] << cn.
54
+ #
55
+ def [] name
56
+ name = name.to_s.downcase.intern unless name.is_a?(Symbol)
57
+ @myhash[name]
58
+ end
59
+
60
+ def dn
61
+ self[:dn][0]
62
+ end
63
+
64
+ def attribute_names
65
+ @myhash.keys
66
+ end
67
+
68
+ def each
69
+ if block_given?
70
+ attribute_names.each {|a| yield a, self[a] }
71
+ end
72
+ end
73
+
74
+ alias_method :each_attribute, :each
75
+
76
+ end # class Entry
77
+
78
+
79
+ end # class LDAP
80
+ end # module Net
81
+
82
+
@@ -0,0 +1,279 @@
1
+ # $Id: filter.rb 78 2006-04-26 02:57:34Z blackhedd $
2
+ #
3
+ #
4
+ #----------------------------------------------------------------------------
5
+ #
6
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
7
+ #
8
+ # Gmail: garbagecat10
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
+ #
24
+ #---------------------------------------------------------------------------
25
+ #
26
+ #
27
+
28
+
29
+ module Net
30
+ class LDAP
31
+
32
+
33
+ # Class Net::LDAP::Filter is used to constrain
34
+ # LDAP searches. An object of this class is
35
+ # passed to Net::LDAP#search in the parameter :filter.
36
+ #
37
+ # Net::LDAP::Filter supports the complete set of search filters
38
+ # available in LDAP, including conjunction, disjunction and negation
39
+ # (AND, OR, and NOT). This class supplants the (infamous) RFC-2254
40
+ # standard notation for specifying LDAP search filters.
41
+ #
42
+ # Here's how to code the familiar "objectclass is present" filter:
43
+ # f = Net::LDAP::Filter.pres( "objectclass" )
44
+ # The object returned by this code can be passed directly to
45
+ # the <tt>:filter</tt> parameter of Net::LDAP#search.
46
+ #
47
+ # See the individual class and instance methods below for more examples.
48
+ #
49
+ class Filter
50
+
51
+ def initialize op, a, b
52
+ @op = op
53
+ @left = a
54
+ @right = b
55
+ end
56
+
57
+ # #eq creates a filter object indicating that the value of
58
+ # a paticular attribute must be either <i>present</i> or must
59
+ # match a particular string.
60
+ #
61
+ # To specify that an attribute is "present" means that only
62
+ # directory entries which contain a value for the particular
63
+ # attribute will be selected by the filter. This is useful
64
+ # in case of optional attributes such as <tt>mail.</tt>
65
+ # Presence is indicated by giving the value "*" in the second
66
+ # parameter to #eq. This example selects only entries that have
67
+ # one or more values for <tt>sAMAccountName:</tt>
68
+ # f = Net::LDAP::Filter.eq( "sAMAccountName", "*" )
69
+ #
70
+ # To match a particular range of values, pass a string as the
71
+ # second parameter to #eq. The string may contain one or more
72
+ # "*" characters as wildcards: these match zero or more occurrences
73
+ # of any character. Full regular-expressions are <i>not</i> supported
74
+ # due to limitations in the underlying LDAP protocol.
75
+ # This example selects any entry with a <tt>mail</tt> value containing
76
+ # the substring "anderson":
77
+ # f = Net::LDAP::Filter.eq( "mail", "*anderson*" )
78
+ #
79
+ def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
80
+ def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
81
+ def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
82
+ def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
83
+ def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
84
+ def Filter::le attribute, value; Filter.new :le, attribute, value; end
85
+
86
+ # #pres( attribute ) is a synonym for #eq( attribute, "*" )
87
+ #
88
+ def Filter::pres attribute; Filter.eq attribute, "*"; end
89
+
90
+ # operator & ("AND") is used to conjoin two or more filters.
91
+ # This expression will select only entries that have an <tt>objectclass</tt>
92
+ # attribute AND have a <tt>mail</tt> attribute that begins with "George":
93
+ # f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" )
94
+ #
95
+ def & filter; Filter.new :and, self, filter; end
96
+
97
+ # operator | ("OR") is used to disjoin two or more filters.
98
+ # This expression will select entries that have either an <tt>objectclass</tt>
99
+ # attribute OR a <tt>mail</tt> attribute that begins with "George":
100
+ # f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" )
101
+ #
102
+ def | filter; Filter.new :or, self, filter; end
103
+
104
+
105
+ #
106
+ # operator ~ ("NOT") is used to negate a filter.
107
+ # This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt>
108
+ # attribute:
109
+ # f = ~ Net::LDAP::Filter.pres( "objectclass" )
110
+ #
111
+ #--
112
+ # This operator can't be !, evidently. Try it.
113
+ def ~@; Filter.new :not, self, nil; end
114
+
115
+
116
+ def to_s
117
+ case @op
118
+ when :ne
119
+ "(!(#{@left}=#{@right}))"
120
+ when :eq
121
+ "(#{@left}=#{@right})"
122
+ when :gt
123
+ "#{@left}>#{@right}"
124
+ when :lt
125
+ "#{@left}<#{@right}"
126
+ when :ge
127
+ "#{@left}>=#{@right}"
128
+ when :le
129
+ "#{@left}<=#{@right}"
130
+ when :and
131
+ "(&(#{@left})(#{@right}))"
132
+ when :or
133
+ "(|(#{@left})(#{@right}))"
134
+ when :not
135
+ "(!(#{@left}))"
136
+ else
137
+ raise "invalid or unsupported operator in LDAP Filter"
138
+ end
139
+ end
140
+
141
+
142
+ #--
143
+ # to_ber
144
+ # Filter ::=
145
+ # CHOICE {
146
+ # and [0] SET OF Filter,
147
+ # or [1] SET OF Filter,
148
+ # not [2] Filter,
149
+ # equalityMatch [3] AttributeValueAssertion,
150
+ # substrings [4] SubstringFilter,
151
+ # greaterOrEqual [5] AttributeValueAssertion,
152
+ # lessOrEqual [6] AttributeValueAssertion,
153
+ # present [7] AttributeType,
154
+ # approxMatch [8] AttributeValueAssertion
155
+ # }
156
+ #
157
+ # SubstringFilter
158
+ # SEQUENCE {
159
+ # type AttributeType,
160
+ # SEQUENCE OF CHOICE {
161
+ # initial [0] LDAPString,
162
+ # any [1] LDAPString,
163
+ # final [2] LDAPString
164
+ # }
165
+ # }
166
+ #
167
+ # Parsing substrings is a little tricky.
168
+ # We use the split method to break a string into substrings
169
+ # delimited by the * (star) character. But we also need
170
+ # to know whether there is a star at the head and tail
171
+ # of the string. A Ruby particularity comes into play here:
172
+ # if you split on * and the first character of the string is
173
+ # a star, then split will return an array whose first element
174
+ # is an _empty_ string. But if the _last_ character of the
175
+ # string is star, then split will return an array that does
176
+ # _not_ add an empty string at the end. So we have to deal
177
+ # with all that specifically.
178
+ #
179
+ def to_ber
180
+ case @op
181
+ when :eq
182
+ if @right == "*" # present
183
+ @left.to_ber_contextspecific 7
184
+ elsif @right =~ /[\*]/ #substring
185
+ ary = @right.split( /[\*]+/ )
186
+ final_star = @right =~ /[\*]$/
187
+ initial_star = ary.first == "" and ary.shift
188
+
189
+ seq = []
190
+ unless initial_star
191
+ seq << ary.shift.to_ber_contextspecific(0)
192
+ end
193
+ n_any_strings = ary.length - (final_star ? 0 : 1)
194
+ p n_any_strings
195
+ n_any_strings.times {
196
+ seq << ary.shift.to_ber_contextspecific(1)
197
+ }
198
+ unless final_star
199
+ seq << ary.shift.to_ber_contextspecific(2)
200
+ end
201
+ [@left.to_ber, seq.to_ber].to_ber_contextspecific 4
202
+ else #equality
203
+ [@left.to_ber, @right.to_ber].to_ber_contextspecific 3
204
+ end
205
+ when :and
206
+ ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
207
+ ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
208
+ when :or
209
+ ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
210
+ ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 )
211
+ when :not
212
+ [@left.to_ber].to_ber_contextspecific 2
213
+ else
214
+ # ERROR, we'll return objectclass=* to keep things from blowing up,
215
+ # but that ain't a good answer and we need to kick out an error of some kind.
216
+ raise "unimplemented search filter"
217
+ end
218
+ end
219
+
220
+ #--
221
+ # coalesce
222
+ # This is a private helper method for dealing with chains of ANDs and ORs
223
+ # that are longer than two. If BOTH of our branches are of the specified
224
+ # type of joining operator, then return both of them as an array (calling
225
+ # coalesce recursively). If they're not, then return an array consisting
226
+ # only of self.
227
+ #
228
+ def coalesce operator
229
+ if @op == operator
230
+ [@left.coalesce( operator ), @right.coalesce( operator )]
231
+ else
232
+ [self]
233
+ end
234
+ end
235
+
236
+
237
+
238
+ #--
239
+ # We get a Ruby object which comes from parsing an RFC-1777 "Filter"
240
+ # object. Convert it to a Net::LDAP::Filter.
241
+ # TODO, we're hardcoding the RFC-1777 BER-encodings of the various
242
+ # filter types. Could pull them out into a constant.
243
+ #
244
+ def Filter::parse_ldap_filter obj
245
+ case obj.ber_identifier
246
+ when 0x87 # present. context-specific primitive 7.
247
+ Filter.eq( obj.to_s, "*" )
248
+ when 0xa3 # equalityMatch. context-specific constructed 3.
249
+ Filter.eq( obj[0], obj[1] )
250
+ else
251
+ raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" )
252
+ end
253
+ end
254
+
255
+
256
+ #--
257
+ # We got a hash of attribute values.
258
+ # Do we match the attributes?
259
+ # Return T/F, and call match recursively as necessary.
260
+ def match entry
261
+ case @op
262
+ when :eq
263
+ if @right == "*"
264
+ l = entry[@left] and l.length > 0
265
+ else
266
+ l = entry[@left] and l = l.to_a and l.index(@right)
267
+ end
268
+ else
269
+ raise LdapError.new( "unknown filter type in match: #{@op}" )
270
+ end
271
+ end
272
+
273
+
274
+ end # class Net::LDAP::Filter
275
+
276
+ end # class Net::LDAP
277
+ end # module Net
278
+
279
+