Skip to content

New filter type to allow searching of binary data #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 28, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Contributors.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Contributions since:
* Derek Harmel (derekharmel)
* Erik Hetzner (egh)
* nowhereman
* David J. Lee (DavidJLee)
8 changes: 8 additions & 0 deletions lib/net/ber/core_ext/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ def to_ber(code = 0x04)
[code].pack('C') + raw_string.length.to_ber_length_encoding + raw_string
end

##
# Converts a string to a BER string but does *not* encode to UTF-8 first.
# This is required for proper representation of binary data for Microsoft
# Active Directory
def to_ber_bin(code = 0x04)
[code].pack('C') + length.to_ber_length_encoding + self
end

def raw_utf8_encoded
if self.respond_to?(:encode)
# Strings should be UTF-8 encoded according to LDAP.
Expand Down
14 changes: 13 additions & 1 deletion lib/net/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ class LdapError < StandardError; end
DefaultPort = 389
DefaultAuth = { :method => :anonymous }
DefaultTreebase = "dc=com"
DefaultForceNoPage = false

StartTlsOid = "1.3.6.1.4.1.1466.20037"

Expand Down Expand Up @@ -370,6 +371,8 @@ def self.result2string(code) #:nodoc:
# specifying the Hash {:method => :simple_tls}. There is a fairly large
# range of potential values that may be given for this parameter. See
# #encryption for details.
# * :force_no_page => Set to true to prevent paged results even if your
# server says it supports them. This is a fix for MS Active Directory
#
# Instantiating a Net::LDAP object does <i>not</i> result in network
# traffic to the LDAP server. It simply stores the connection and binding
Expand All @@ -380,6 +383,7 @@ def initialize(args = {})
@verbose = false # Make this configurable with a switch on the class.
@auth = args[:auth] || DefaultAuth
@base = args[:base] || DefaultTreebase
@force_no_page = args[:force_no_page] || DefaultForceNoPage
encryption args[:encryption] # may be nil

if pr = @auth[:password] and pr.respond_to?(:call)
Expand Down Expand Up @@ -1092,6 +1096,10 @@ def search_subschema_entry
# MUST refactor the root_dse call out.
#++
def paged_searches_supported?
# active directory returns that it supports paged results. However
# it returns binary data in the rfc2696_cookie which throws an
# encoding exception breaking searching.
return false if @force_no_page
@server_caps ||= search_root_dse
@server_caps[:supportedcontrol].include?(Net::LDAP::LdapControls::PagedResults)
end
Expand Down Expand Up @@ -1387,6 +1395,10 @@ def search(args = {})
search_attributes.to_ber_sequence
].to_ber_appsequence(3)

# rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
# this breaks when calling to_ber. (Can't force binary data to UTF-8)
# we have to disable paging (even though server supports it) to get around this...

controls = []
controls <<
[
Expand Down Expand Up @@ -1530,7 +1542,7 @@ def add(args)
#--
# TODO: need to support a time limit, in case the server fails to respond.
#++
def rename args
def rename(args)
old_dn = args[:olddn] or raise "Unable to rename empty DN"
new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
delete_attrs = args[:delete_attributes] ? true : false
Expand Down
24 changes: 23 additions & 1 deletion lib/net/ldap/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
class Net::LDAP::Filter
##
# Known filter types.
FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex ]
FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq ]

def initialize(op, left, right) #:nodoc:
unless FilterTypes.include?(op)
Expand Down Expand Up @@ -65,6 +65,23 @@ def eq(attribute, value)
new(:eq, attribute, value)
end

##
# Creates a Filter object indicating a binary comparison.
# this prevents the search data from being forced into a UTF-8 string.
#
# This is primarily used for Microsoft Active Directory to compare
# GUID values.
#
# # for guid represented as hex charecters
# guid = "6a31b4a12aa27a41aca9603f27dd5116"
# guid_bin = [guid].pack("H*")
# f = Net::LDAP::Filter.bineq("objectGUID", guid_bin)
#
# This filter does not perform any escaping.
def bineq(attribute, value)
new(:bineq, attribute, value)
end

##
# Creates a Filter object indicating extensible comparison. This Filter
# object is currently considered EXPERIMENTAL.
Expand Down Expand Up @@ -399,6 +416,8 @@ def to_raw_rfc2254
"!(#{@left}=#{@right})"
when :eq
"#{@left}=#{@right}"
when :bineq
"#{@left}=#{@right}"
when :ex
"#{@left}:=#{@right}"
when :ge
Expand Down Expand Up @@ -508,6 +527,9 @@ def to_ber
else # equality
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(3)
end
when :bineq
# make sure data is not forced to UTF-8
[@left.to_s.to_ber, unescape(@right).to_ber_bin].to_ber_contextspecific(3)
when :ex
seq = []

Expand Down
5 changes: 5 additions & 0 deletions spec/unit/ber/ber_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
it "should properly encode strings encodable as UTF-8" do
"teststring".encode("US-ASCII").to_ber.should == "\x04\nteststring"
end
it "should properly encode binary data strings using to_ber_bin" do
# This is used for searching for GUIDs in Active Directory
["6a31b4a12aa27a41aca9603f27dd5116"].pack("H*").to_ber_bin.should ==
"\x04\x10" + "j1\xB4\xA1*\xA2zA\xAC\xA9`?'\xDDQ\x16"
end
it "should fail on strings that can not be converted to UTF-8" do
error = Encoding::UndefinedConversionError
lambda {"\x81".to_ber }.should raise_exception(error)
Expand Down