diff --git a/Rakefile b/Rakefile index 51e58d1..f146781 100644 --- a/Rakefile +++ b/Rakefile @@ -75,6 +75,11 @@ task :test_snmp do |t| run_test_set t, ['tests/testsnmp.rb', 'tests/testber.rb'] end +desc "(Provisional) Run tests for filters" +task :test_filters do |t| + run_test_set t, ['tests/testfilter.rb'] +end + spec = eval(File.read("net-ldap.gemspec")) spec.version = $version desc "Build the RubyGem for #$name." diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index eb4fa24..437938c 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -311,6 +311,8 @@ module Net 2 => :array, # SearchFilter-NOT 3 => :array, # Seach referral 4 => :array, # unknown use in Microsoft Outlook + 5 => :array, # SearchFilter-GE + 6 => :array, # SearchFilter-LE 7 => :array, # serverSaslCreds } } diff --git a/lib/net/ldap/filter.rb b/lib/net/ldap/filter.rb index f97a802..58d70a6 100644 --- a/lib/net/ldap/filter.rb +++ b/lib/net/ldap/filter.rb @@ -115,6 +115,11 @@ class Filter # Removed GT and LT. They're not in the RFC. def ~@; Filter.new :not, self, nil; end + # Equality operator for filters, useful primarily for constructing unit tests. + def == filter + str = "[@op,@left,@right]" + self.instance_eval(str) == filter.instance_eval(str) + end def to_s case @op @@ -224,6 +229,58 @@ class Filter end end + + # Converts an LDAP search filter in BER format to an Net::LDAP::Filter + # object. The incoming BER object most likely came to us by parsing an + # LDAP searchRequest PDU. + # Cf the comments under #to_ber, including the grammar snippet from the RFC. + #-- + # We're hardcoding the BER constants from the RFC. Ought to break them out + # into constants. + # + def Filter::parse_ber ber + case ber.ber_identifier + when 0xa0 # context-specific constructed 0, "and" + ber.map {|b| Filter::parse_ber(b)}.inject {|memo,obj| memo & obj} + when 0xa1 # context-specific constructed 1, "or" + ber.map {|b| Filter::parse_ber(b)}.inject {|memo,obj| memo | obj} + when 0xa2 # context-specific constructed 2, "not" + ~ Filter::parse_ber( ber.first ) + when 0xa3 # context-specific constructed 3, "equalityMatch" + if ber.last == "*" + else + Filter.eq( ber.first, ber.last ) + end + when 0xa4 # context-specific constructed 4, "substring" + str = "" + final = false + ber.last.each {|b| + case b.ber_identifier + when 0x80 # context-specific primitive 0, SubstringFilter "initial" + raise "unrecognized substring filter, bad initial" if str.length > 0 + str += b + when 0x81 # context-specific primitive 0, SubstringFilter "any" + str += "*#{b}" + when 0x82 # context-specific primitive 0, SubstringFilter "final" + str += "*#{b}" + final = true + end + } + str += "*" unless final + Filter.eq( ber.first.to_s, str ) + when 0xa5 # context-specific constructed 5, "greaterOrEqual" + Filter.ge( ber.first.to_s, ber.last.to_s ) + when 0xa6 # context-specific constructed 5, "lessOrEqual" + Filter.le( ber.first.to_s, ber.last.to_s ) + when 0x87 # context-specific primitive 7, "present" + # call to_s to get rid of the BER-identifiedness of the incoming string. + Filter.pres( ber.to_s ) + else + raise "invalid BER tag-value (#{ber.ber_identifier}) in search filter" + end + end + + #-- # coalesce # This is a private helper method for dealing with chains of ANDs and ORs @@ -260,6 +317,8 @@ class Filter end + + #-- # We got a hash of attribute values. # Do we match the attributes? diff --git a/tests/testfilter.rb b/tests/testfilter.rb index 4339f26..34876c7 100644 --- a/tests/testfilter.rb +++ b/tests/testfilter.rb @@ -11,27 +11,88 @@ require 'net/ldap' class TestFilter < Test::Unit::TestCase - def setup - end + def setup + end - def teardown - end + def teardown + end - def test_rfc_2254 - p Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " ) - p Net::LDAP::Filter.from_rfc2254( "uid!=george*" ) - p Net::LDAP::Filter.from_rfc2254( "uidgeorge*" ) - p Net::LDAP::Filter.from_rfc2254( "uid>=george*" ) - p Net::LDAP::Filter.from_rfc2254( "uid!=george*" ) + # Note that the RFC doesn't define either less-than or greater-than. + def test_rfc_2254 + Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " ) + Net::LDAP::Filter.from_rfc2254( "uid!=george*" ) + Net::LDAP::Filter.from_rfc2254( "uid <= george*" ) + Net::LDAP::Filter.from_rfc2254( "uid>=george*" ) + Net::LDAP::Filter.from_rfc2254( "uid!=george*" ) - p Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" ) - p Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" ) - p Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" ) - end + Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" ) + Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" ) + Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" ) + end + def test_filters_from_ber + [ + Net::LDAP::Filter.eq( "objectclass", "*" ), + Net::LDAP::Filter.pres( "objectclass" ), + Net::LDAP::Filter.eq( "objectclass", "ou" ), + Net::LDAP::Filter.ge( "uid", "500" ), + Net::LDAP::Filter.le( "uid", "500" ), + (~ Net::LDAP::Filter.pres( "objectclass" )), + (Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.pres( "ou" )), + (Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.pres( "ou" ) & Net::LDAP::Filter.pres("sn")), + (Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.pres( "ou" ) | Net::LDAP::Filter.pres("sn")), + + Net::LDAP::Filter.eq( "objectclass", "*aaa" ), + Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb" ), + Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*ccc" ), + Net::LDAP::Filter.eq( "objectclass", "aaa*bbb" ), + Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*ccc" ), + Net::LDAP::Filter.eq( "objectclass", "abc*def*1111*22*g" ), + Net::LDAP::Filter.eq( "objectclass", "*aaa*" ), + Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*" ), + Net::LDAP::Filter.eq( "objectclass", "*aaa*bbb*ccc*" ), + Net::LDAP::Filter.eq( "objectclass", "aaa*" ), + Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*" ), + Net::LDAP::Filter.eq( "objectclass", "aaa*bbb*ccc*" ), + ].each {|ber| + f = Net::LDAP::Filter.parse_ber( ber.to_ber.read_ber( Net::LDAP::AsnSyntax) ) + assert( f == ber ) + assert_equal( f.to_ber, ber.to_ber ) + } + + end + + def test_ber_from_rfc2254_filter + [ + Net::LDAP::Filter.construct( "objectclass=*" ), + Net::LDAP::Filter.construct("objectclass=ou" ), + Net::LDAP::Filter.construct("uid >= 500" ), + Net::LDAP::Filter.construct("uid <= 500" ), + Net::LDAP::Filter.construct("(!(uid=*))" ), + Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*))" ), + Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*)(sn=*))" ), + Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*))" ), + Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*)(sn=*))" ), + + Net::LDAP::Filter.construct("objectclass=*aaa"), + Net::LDAP::Filter.construct("objectclass=*aaa*bbb"), + Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc"), + Net::LDAP::Filter.construct("objectclass=aaa*bbb"), + Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc"), + Net::LDAP::Filter.construct("objectclass=abc*def*1111*22*g"), + Net::LDAP::Filter.construct("objectclass=*aaa*"), + Net::LDAP::Filter.construct("objectclass=*aaa*bbb*"), + Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc*"), + Net::LDAP::Filter.construct("objectclass=aaa*"), + Net::LDAP::Filter.construct("objectclass=aaa*bbb*"), + Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc*"), + ].each {|ber| + f = Net::LDAP::Filter.parse_ber( ber.to_ber.read_ber( Net::LDAP::AsnSyntax) ) + assert( f == ber ) + assert_equal( f.to_ber, ber.to_ber ) + } + end end