Skip to content

Commit da882af

Browse files
author
blackhedd
committed
Added Net::LDAP::Filter.parse_ber and associated helper methods and tests.
1 parent bc1129f commit da882af

File tree

4 files changed

+143
-16
lines changed

4 files changed

+143
-16
lines changed

Rakefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ task :test_snmp do |t|
7575
run_test_set t, ['tests/testsnmp.rb', 'tests/testber.rb']
7676
end
7777

78+
desc "(Provisional) Run tests for filters"
79+
task :test_filters do |t|
80+
run_test_set t, ['tests/testfilter.rb']
81+
end
82+
7883
spec = eval(File.read("net-ldap.gemspec"))
7984
spec.version = $version
8085
desc "Build the RubyGem for #$name."

lib/net/ldap.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ class LdapError < Exception; end
311311
2 => :array, # SearchFilter-NOT
312312
3 => :array, # Seach referral
313313
4 => :array, # unknown use in Microsoft Outlook
314+
5 => :array, # SearchFilter-GE
315+
6 => :array, # SearchFilter-LE
314316
7 => :array, # serverSaslCreds
315317
}
316318
}

lib/net/ldap/filter.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ def | filter; Filter.new :or, self, filter; end
115115
# Removed GT and LT. They're not in the RFC.
116116
def ~@; Filter.new :not, self, nil; end
117117

118+
# Equality operator for filters, useful primarily for constructing unit tests.
119+
def == filter
120+
str = "[@op,@left,@right]"
121+
self.instance_eval(str) == filter.instance_eval(str)
122+
end
118123

119124
def to_s
120125
case @op
@@ -224,6 +229,58 @@ def to_ber
224229
end
225230
end
226231

232+
233+
# Converts an LDAP search filter in BER format to an Net::LDAP::Filter
234+
# object. The incoming BER object most likely came to us by parsing an
235+
# LDAP searchRequest PDU.
236+
# Cf the comments under #to_ber, including the grammar snippet from the RFC.
237+
#--
238+
# We're hardcoding the BER constants from the RFC. Ought to break them out
239+
# into constants.
240+
#
241+
def Filter::parse_ber ber
242+
case ber.ber_identifier
243+
when 0xa0 # context-specific constructed 0, "and"
244+
ber.map {|b| Filter::parse_ber(b)}.inject {|memo,obj| memo & obj}
245+
when 0xa1 # context-specific constructed 1, "or"
246+
ber.map {|b| Filter::parse_ber(b)}.inject {|memo,obj| memo | obj}
247+
when 0xa2 # context-specific constructed 2, "not"
248+
~ Filter::parse_ber( ber.first )
249+
when 0xa3 # context-specific constructed 3, "equalityMatch"
250+
if ber.last == "*"
251+
else
252+
Filter.eq( ber.first, ber.last )
253+
end
254+
when 0xa4 # context-specific constructed 4, "substring"
255+
str = ""
256+
final = false
257+
ber.last.each {|b|
258+
case b.ber_identifier
259+
when 0x80 # context-specific primitive 0, SubstringFilter "initial"
260+
raise "unrecognized substring filter, bad initial" if str.length > 0
261+
str += b
262+
when 0x81 # context-specific primitive 0, SubstringFilter "any"
263+
str += "*#{b}"
264+
when 0x82 # context-specific primitive 0, SubstringFilter "final"
265+
str += "*#{b}"
266+
final = true
267+
end
268+
}
269+
str += "*" unless final
270+
Filter.eq( ber.first.to_s, str )
271+
when 0xa5 # context-specific constructed 5, "greaterOrEqual"
272+
Filter.ge( ber.first.to_s, ber.last.to_s )
273+
when 0xa6 # context-specific constructed 5, "lessOrEqual"
274+
Filter.le( ber.first.to_s, ber.last.to_s )
275+
when 0x87 # context-specific primitive 7, "present"
276+
# call to_s to get rid of the BER-identifiedness of the incoming string.
277+
Filter.pres( ber.to_s )
278+
else
279+
raise "invalid BER tag-value (#{ber.ber_identifier}) in search filter"
280+
end
281+
end
282+
283+
227284
#--
228285
# coalesce
229286
# This is a private helper method for dealing with chains of ANDs and ORs
@@ -260,6 +317,8 @@ def Filter::parse_ldap_filter obj
260317
end
261318

262319

320+
321+
263322
#--
264323
# We got a hash of attribute values.
265324
# Do we match the attributes?

tests/testfilter.rb

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,88 @@
1111

1212
class TestFilter < Test::Unit::TestCase
1313

14-
def setup
15-
end
14+
def setup
15+
end
1616

1717

18-
def teardown
19-
end
18+
def teardown
19+
end
2020

21-
def test_rfc_2254
22-
p Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " )
23-
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
24-
p Net::LDAP::Filter.from_rfc2254( "uid<george*" )
25-
p Net::LDAP::Filter.from_rfc2254( "uid <= george*" )
26-
p Net::LDAP::Filter.from_rfc2254( "uid>george*" )
27-
p Net::LDAP::Filter.from_rfc2254( "uid>=george*" )
28-
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
21+
# Note that the RFC doesn't define either less-than or greater-than.
22+
def test_rfc_2254
23+
Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " )
24+
Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
25+
Net::LDAP::Filter.from_rfc2254( "uid <= george*" )
26+
Net::LDAP::Filter.from_rfc2254( "uid>=george*" )
27+
Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
2928

30-
p Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" )
31-
p Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" )
32-
p Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" )
33-
end
29+
Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" )
30+
Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" )
31+
Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" )
32+
end
3433

34+
def test_filters_from_ber
35+
[
36+
Net::LDAP::Filter.eq( "objectclass", "*" ),
37+
Net::LDAP::Filter.pres( "objectclass" ),
38+
Net::LDAP::Filter.eq( "objectclass", "ou" ),
39+
Net::LDAP::Filter.ge( "uid", "500" ),
40+
Net::LDAP::Filter.le( "uid", "500" ),
41+
(~ Net::LDAP::Filter.pres( "objectclass" )),
42+
(Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.pres( "ou" )),
43+
(Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.pres( "ou" ) & Net::LDAP::Filter.pres("sn")),
44+
(Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.pres( "ou" ) | Net::LDAP::Filter.pres("sn")),
45+
46+
Net::LDAP::Filter.eq( "objectclass", "*aaa" ),
47+
Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb" ),
48+
Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*ccc" ),
49+
Net::LDAP::Filter.eq( "objectclass", "aaa*bbb" ),
50+
Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*ccc" ),
51+
Net::LDAP::Filter.eq( "objectclass", "abc*def*1111*22*g" ),
52+
Net::LDAP::Filter.eq( "objectclass", "*aaa*" ),
53+
Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*" ),
54+
Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*ccc*" ),
55+
Net::LDAP::Filter.eq( "objectclass", "aaa*" ),
56+
Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*" ),
57+
Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*ccc*" ),
58+
].each {|ber|
59+
f = Net::LDAP::Filter.parse_ber( ber.to_ber.read_ber( Net::LDAP::AsnSyntax) )
60+
assert( f == ber )
61+
assert_equal( f.to_ber, ber.to_ber )
62+
}
63+
64+
end
65+
66+
def test_ber_from_rfc2254_filter
67+
[
68+
Net::LDAP::Filter.construct( "objectclass=*" ),
69+
Net::LDAP::Filter.construct("objectclass=ou" ),
70+
Net::LDAP::Filter.construct("uid >= 500" ),
71+
Net::LDAP::Filter.construct("uid <= 500" ),
72+
Net::LDAP::Filter.construct("(!(uid=*))" ),
73+
Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*))" ),
74+
Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*)(sn=*))" ),
75+
Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*))" ),
76+
Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*)(sn=*))" ),
77+
78+
Net::LDAP::Filter.construct("objectclass=*aaa"),
79+
Net::LDAP::Filter.construct("objectclass=*aaa*bbb"),
80+
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc"),
81+
Net::LDAP::Filter.construct("objectclass=aaa*bbb"),
82+
Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc"),
83+
Net::LDAP::Filter.construct("objectclass=abc*def*1111*22*g"),
84+
Net::LDAP::Filter.construct("objectclass=*aaa*"),
85+
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*"),
86+
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc*"),
87+
Net::LDAP::Filter.construct("objectclass=aaa*"),
88+
Net::LDAP::Filter.construct("objectclass=aaa*bbb*"),
89+
Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc*"),
90+
].each {|ber|
91+
f = Net::LDAP::Filter.parse_ber( ber.to_ber.read_ber( Net::LDAP::AsnSyntax) )
92+
assert( f == ber )
93+
assert_equal( f.to_ber, ber.to_ber )
94+
}
95+
end
3596

3697
end
3798

0 commit comments

Comments
 (0)