ruby-net-ldap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+