OSDN Git Service

add Redmine trunk rev 3089
[redminele/redminele.git] / redmine / vendor / plugins / ruby-net-ldap-0.0.4 / lib / net / ldap / filter.rb
1 # $Id: filter.rb 151 2006-08-15 08:34:53Z 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   # Removed gt and lt. They ain't in the standard!
80   #
81   def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
82   def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
83   #def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
84   #def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
85   def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
86   def Filter::le attribute, value; Filter.new :le, attribute, value; end
87
88   # #pres( attribute ) is a synonym for #eq( attribute, "*" )
89   #
90   def Filter::pres attribute; Filter.eq attribute, "*"; end
91
92   # operator & ("AND") is used to conjoin two or more filters.
93   # This expression will select only entries that have an <tt>objectclass</tt>
94   # attribute AND have a <tt>mail</tt> attribute that begins with "George":
95   #  f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" )
96   #
97   def & filter; Filter.new :and, self, filter; end
98
99   # operator | ("OR") is used to disjoin two or more filters.
100   # This expression will select entries that have either an <tt>objectclass</tt>
101   # attribute OR a <tt>mail</tt> attribute that begins with "George":
102   #  f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" )
103   #
104   def | filter; Filter.new :or, self, filter; end
105
106
107   #
108   # operator ~ ("NOT") is used to negate a filter.
109   # This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt>
110   # attribute:
111   #  f = ~ Net::LDAP::Filter.pres( "objectclass" )
112   #
113   #--
114   # This operator can't be !, evidently. Try it.
115   # Removed GT and LT. They're not in the RFC.
116   def ~@; Filter.new :not, self, nil; end
117
118
119   def to_s
120     case @op
121     when :ne
122       "(!(#{@left}=#{@right}))"
123     when :eq
124       "(#{@left}=#{@right})"
125     #when :gt
126      # "#{@left}>#{@right}"
127     #when :lt
128      # "#{@left}<#{@right}"
129     when :ge
130       "#{@left}>=#{@right}"
131     when :le
132       "#{@left}<=#{@right}"
133     when :and
134       "(&(#{@left})(#{@right}))"
135     when :or
136       "(|(#{@left})(#{@right}))"
137     when :not
138       "(!(#{@left}))"
139     else
140       raise "invalid or unsupported operator in LDAP Filter"
141     end
142   end
143
144
145   #--
146   # to_ber
147   # Filter ::=
148   #     CHOICE {
149   #         and            [0] SET OF Filter,
150   #         or             [1] SET OF Filter,
151   #         not            [2] Filter,
152   #         equalityMatch  [3] AttributeValueAssertion,
153   #         substrings     [4] SubstringFilter,
154   #         greaterOrEqual [5] AttributeValueAssertion,
155   #         lessOrEqual    [6] AttributeValueAssertion,
156   #         present        [7] AttributeType,
157   #         approxMatch    [8] AttributeValueAssertion
158   #     }
159   #
160   # SubstringFilter
161   #     SEQUENCE {
162   #         type               AttributeType,
163   #         SEQUENCE OF CHOICE {
164   #             initial        [0] LDAPString,
165   #             any            [1] LDAPString,
166   #             final          [2] LDAPString
167   #         }
168   #     }
169   #
170   # Parsing substrings is a little tricky.
171   # We use the split method to break a string into substrings
172   # delimited by the * (star) character. But we also need
173   # to know whether there is a star at the head and tail
174   # of the string. A Ruby particularity comes into play here:
175   # if you split on * and the first character of the string is
176   # a star, then split will return an array whose first element
177   # is an _empty_ string. But if the _last_ character of the
178   # string is star, then split will return an array that does
179   # _not_ add an empty string at the end. So we have to deal
180   # with all that specifically.
181   #
182   def to_ber
183     case @op
184     when :eq
185       if @right == "*"          # present
186         @left.to_s.to_ber_contextspecific 7
187       elsif @right =~ /[\*]/    #substring
188         ary = @right.split( /[\*]+/ )
189         final_star = @right =~ /[\*]$/
190         initial_star = ary.first == "" and ary.shift
191
192         seq = []
193         unless initial_star
194           seq << ary.shift.to_ber_contextspecific(0)
195         end
196         n_any_strings = ary.length - (final_star ? 0 : 1)
197         #p n_any_strings
198         n_any_strings.times {
199           seq << ary.shift.to_ber_contextspecific(1)
200         }
201         unless final_star
202           seq << ary.shift.to_ber_contextspecific(2)
203         end
204         [@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4
205       else                      #equality
206         [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3
207       end
208     when :ge
209       [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5
210     when :le
211       [@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6
212     when :and
213       ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
214       ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
215     when :or
216       ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
217       ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 )
218     when :not
219         [@left.to_ber].to_ber_contextspecific 2
220     else
221       # ERROR, we'll return objectclass=* to keep things from blowing up,
222       # but that ain't a good answer and we need to kick out an error of some kind.
223       raise "unimplemented search filter"
224     end
225   end
226
227   #--
228   # coalesce
229   # This is a private helper method for dealing with chains of ANDs and ORs
230   # that are longer than two. If BOTH of our branches are of the specified
231   # type of joining operator, then return both of them as an array (calling
232   # coalesce recursively). If they're not, then return an array consisting
233   # only of self.
234   #
235   def coalesce operator
236     if @op == operator
237       [@left.coalesce( operator ), @right.coalesce( operator )]
238     else
239       [self]
240     end
241   end
242
243
244
245   #--
246   # We get a Ruby object which comes from parsing an RFC-1777 "Filter"
247   # object. Convert it to a Net::LDAP::Filter.
248   # TODO, we're hardcoding the RFC-1777 BER-encodings of the various
249   # filter types. Could pull them out into a constant.
250   #
251   def Filter::parse_ldap_filter obj
252     case obj.ber_identifier
253     when 0x87         # present. context-specific primitive 7.
254       Filter.eq( obj.to_s, "*" )
255     when 0xa3         # equalityMatch. context-specific constructed 3.
256       Filter.eq( obj[0], obj[1] )
257     else
258       raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" )
259     end
260   end
261
262
263   #--
264   # We got a hash of attribute values.
265   # Do we match the attributes?
266   # Return T/F, and call match recursively as necessary.
267   def match entry
268     case @op
269     when :eq
270       if @right == "*"
271         l = entry[@left] and l.length > 0
272       else
273         l = entry[@left] and l = l.to_a and l.index(@right)
274       end
275     else
276       raise LdapError.new( "unknown filter type in match: #{@op}" )
277     end
278   end
279
280   # Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
281   # to a Net::LDAP::Filter.
282   def self.construct ldap_filter_string
283     FilterParser.new(ldap_filter_string).filter
284   end
285
286   # Synonym for #construct.
287   # to a Net::LDAP::Filter.
288   def self.from_rfc2254 ldap_filter_string
289     construct ldap_filter_string
290   end
291
292 end # class Net::LDAP::Filter
293
294
295
296 class FilterParser #:nodoc:
297
298   attr_reader :filter
299
300   def initialize str
301     require 'strscan'
302     @filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
303   end
304
305   def parse scanner
306     parse_filter_branch(scanner) or parse_paren_expression(scanner)
307   end
308
309   def parse_paren_expression scanner
310     if scanner.scan(/\s*\(\s*/)
311       b = if scanner.scan(/\s*\&\s*/)
312         a = nil
313         branches = []
314         while br = parse_paren_expression(scanner)
315           branches << br
316         end
317         if branches.length >= 2
318           a = branches.shift
319           while branches.length > 0
320             a = a & branches.shift
321           end
322           a
323         end
324       elsif scanner.scan(/\s*\|\s*/)
325         # TODO: DRY!
326         a = nil
327         branches = []
328         while br = parse_paren_expression(scanner)
329           branches << br
330         end
331         if branches.length >= 2
332           a = branches.shift
333           while branches.length > 0
334             a = a | branches.shift
335           end
336           a
337         end
338       elsif scanner.scan(/\s*\!\s*/)
339         br = parse_paren_expression(scanner)
340         if br
341           ~ br
342         end
343       else
344         parse_filter_branch( scanner )
345       end
346
347       if b and scanner.scan( /\s*\)\s*/ )
348         b
349       end
350     end
351   end
352
353   # Added a greatly-augmented filter contributed by Andre Nathan
354   # for detecting special characters in values. (15Aug06)
355   def parse_filter_branch scanner
356     scanner.scan(/\s*/)
357     if token = scanner.scan( /[\w\-_]+/ )
358       scanner.scan(/\s*/)
359       if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ )
360         scanner.scan(/\s*/)
361         #if value = scanner.scan( /[\w\*\.]+/ ) (ORG)
362         if value = scanner.scan( /[\w\*\.\+\-@=#\$%&!]+/ )
363           case op
364           when "="
365             Filter.eq( token, value )
366           when "!="
367             Filter.ne( token, value )
368           when "<"
369             Filter.lt( token, value )
370           when "<="
371             Filter.le( token, value )
372           when ">"
373             Filter.gt( token, value )
374           when ">="
375             Filter.ge( token, value )
376           end
377         end
378       end
379     end
380   end
381
382 end # class Net::LDAP::FilterParser
383
384 end # class Net::LDAP
385 end # module Net
386
387