diff --git a/.gitignore b/.gitignore index a323762..993ac60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.rspec spec/ldap.yml .rvmrc *.gemspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..f284314 --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source "http://rubygems.org" + +gem 'hoe' +gem 'hoe-git' + +group :development do + gem 'metaid' + gem 'rspec', '~> 2.0' + gem 'flexmock' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..96a7b25 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,29 @@ +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.2) + flexmock (0.8.11) + hoe (2.8.0) + rake (>= 0.8.7) + hoe-git (1.3.0) + hoe (>= 2.2.0) + metaid (1.0) + rake (0.8.7) + rspec (2.3.0) + rspec-core (~> 2.3.0) + rspec-expectations (~> 2.3.0) + rspec-mocks (~> 2.3.0) + rspec-core (2.3.1) + rspec-expectations (2.3.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + flexmock + hoe + hoe-git + metaid + rspec (~> 2.0) diff --git a/lib/net/ldap/filter.rb b/lib/net/ldap/filter.rb index 8eec9f0..4e8aec8 100644 --- a/lib/net/ldap/filter.rb +++ b/lib/net/ldap/filter.rb @@ -1,3 +1,4 @@ +# Encoding: UTF-8 # Copyright (C) 2006 by Francis Cianfrocca and other contributors. All # Rights Reserved. # @@ -79,6 +80,8 @@ class Net::LDAP::Filter # mail value containing the substring "anderson": # # f = Net::LDAP::Filter.eq("mail", "*anderson*") + # + # This filter does not perform any escaping def eq(attribute, value) new(:eq, attribute, value) end @@ -136,10 +139,44 @@ class Net::LDAP::Filter # Creates a Filter object indicating that a particular attribute value # is either not present or does not match a particular string; see # Filter::eq for more information. + # + # This filter does not perform any escaping def ne(attribute, value) new(:ne, attribute, value) end + ## + # Creates a Filter object indicating that the value of a particular + # attribute must match a particular string. The attribute value is + # escaped, so the "*" character is interpreted literally. + def equals(attribute, value) + new(:eq, attribute, escape(value)) + end + + ## + # Creates a Filter object indicating that the value of a particular + # attribute must begin with a particular string. The attribute value is + # escaped, so the "*" character is interpreted literally. + def begins(attribute, value) + new(:eq, attribute, escape(value) + "*") + end + + ## + # Creates a Filter object indicating that the value of a particular + # attribute must end with a particular string. The attribute value is + # escaped, so the "*" character is interpreted literally. + def ends(attribute, value) + new(:eq, attribute, "*" + escape(value)) + end + + ## + # Creates a Filter object indicating that the value of a particular + # attribute must contain a particular string. The attribute value is + # escaped, so the "*" character is interpreted literally. + def contains(attribute, value) + new(:eq, attribute, "*" + escape(value) + "*") + end + ## # Creates a Filter object indicating that a particular attribute value # is greater than or equal to the specified value. @@ -207,6 +244,30 @@ class Net::LDAP::Filter alias_method :present, :present? alias_method :pres, :present? + # http://tools.ietf.org/html/rfc4515 lists these exceptions from UTF1 + # charset for filters. All of the following must be escaped in any normal + # string using a single backslash ('\') as escape. + # + ESCAPES = { + '!' => '21', # EXCLAMATION = %x21 ; exclamation mark ("!") + '&' => '26', # AMPERSAND = %x26 ; ampersand (or AND symbol) ("&") + '*' => '2A', # ASTERISK = %x2A ; asterisk ("*") + ':' => '3A', # COLON = %x3A ; colon (":") + '|' => '7C', # VERTBAR = %x7C ; vertical bar (or pipe) ("|") + '~' => '7E', # TILDE = %x7E ; tilde ("~") + } + # Compiled character class regexp using the keys from the above hash. + ESCAPE_RE = Regexp.new( + "[" + + ESCAPES.keys.map { |e| Regexp.escape(e) }.join + + "]") + + ## + # Escape a string for use in an LDAP filter + def escape(string) + string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] } + 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f6c63bc..5079537 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,5 @@ require 'net/ldap' -require 'rubygems' -Spec::Runner.configure do |config| +RSpec.configure do |config| config.mock_with :flexmock end diff --git a/spec/unit/ber/ber_spec.rb b/spec/unit/ber/ber_spec.rb index 9506122..33be2fd 100644 --- a/spec/unit/ber/ber_spec.rb +++ b/spec/unit/ber/ber_spec.rb @@ -4,12 +4,13 @@ require 'net/ber' require 'net/ldap' describe "BER encoding of" do - def properly_encode_and_decode - simple_matcher('properly encode and decode') do |given| + + RSpec::Matchers.define :properly_encode_and_decode do + match do |given| given.to_ber.read_ber.should == given end end - + context "arrays" do it "should properly encode/decode []" do [].should properly_encode_and_decode diff --git a/spec/unit/ldap/filter_spec.rb b/spec/unit/ldap/filter_spec.rb index fac0c28..623f032 100644 --- a/spec/unit/ldap/filter_spec.rb +++ b/spec/unit/ldap/filter_spec.rb @@ -49,5 +49,35 @@ describe Net::LDAP::Filter do Net::LDAP::Filter.construct("uid=O'Keefe").to_rfc2254.should == "(uid=O'Keefe)" end end - + + describe "convenience filter constructors" do + def eq(attribute, value) + described_class.eq(attribute, value) + end + describe "<- .equals(attr, val)" do + it "should delegate to .eq with escaping" do + described_class.equals('dn', 'f*oo').should == eq('dn', 'f\2Aoo') + end + end + describe "<- .begins(attr, val)" do + it "should delegate to .eq with escaping" do + described_class.begins('dn', 'f*oo').should == eq('dn', 'f\2Aoo*') + end + end + describe "<- .ends(attr, val)" do + it "should delegate to .eq with escaping" do + described_class.ends('dn', 'f*oo').should == eq('dn', '*f\2Aoo') + end + end + describe "<- .contains(attr, val)" do + it "should delegate to .eq with escaping" do + described_class.contains('dn', 'f*oo').should == eq('dn', '*f\2Aoo*') + end + end + end + describe "<- .escape(str)" do + it "should escape !, &, *, :, | and ~" do + Net::LDAP::Filter.escape('!&*:|~').should == "\\21\\26\\2A\\3A\\7C\\7E" + end + end end \ No newline at end of file