mirror of
https://github.com/redmine/redmine.git
synced 2025-11-08 06:15:59 +01:00
Replaced ruby-net-ldap with net-ldap 0.2.2 gem.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8751 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
154
vendor/gems/net-ldap-0.2.2/lib/net/ldap/dataset.rb
vendored
Normal file
154
vendor/gems/net-ldap-0.2.2/lib/net/ldap/dataset.rb
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# An LDAP Dataset. Used primarily as an intermediate format for converting
|
||||
# to and from LDIF strings and Net::LDAP::Entry objects.
|
||||
class Net::LDAP::Dataset < Hash
|
||||
##
|
||||
# Dataset object comments.
|
||||
attr_reader :comments
|
||||
|
||||
def initialize(*args, &block) # :nodoc:
|
||||
super
|
||||
@comments = []
|
||||
end
|
||||
|
||||
##
|
||||
# Outputs an LDAP Dataset as an array of strings representing LDIF
|
||||
# entries.
|
||||
def to_ldif
|
||||
ary = []
|
||||
ary += @comments unless @comments.empty?
|
||||
keys.sort.each do |dn|
|
||||
ary << "dn: #{dn}"
|
||||
|
||||
attributes = self[dn].keys.map { |attr| attr.to_s }.sort
|
||||
attributes.each do |attr|
|
||||
self[dn][attr.to_sym].each do |value|
|
||||
if attr == "userpassword" or value_is_binary?(value)
|
||||
value = [value].pack("m").chomp.gsub(/\n/m, "\n ")
|
||||
ary << "#{attr}:: #{value}"
|
||||
else
|
||||
ary << "#{attr}: #{value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ary << ""
|
||||
end
|
||||
block_given? and ary.each { |line| yield line}
|
||||
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# Outputs an LDAP Dataset as an LDIF string.
|
||||
def to_ldif_string
|
||||
to_ldif.join("\n")
|
||||
end
|
||||
|
||||
##
|
||||
# Convert the parsed LDIF objects to Net::LDAP::Entry objects.
|
||||
def to_entries
|
||||
ary = []
|
||||
keys.each do |dn|
|
||||
entry = Net::LDAP::Entry.new(dn)
|
||||
self[dn].each do |attr, value|
|
||||
entry[attr] = value
|
||||
end
|
||||
ary << entry
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# This is an internal convenience method to determine if a value requires
|
||||
# base64-encoding before conversion to LDIF output. The standard approach
|
||||
# in most LDAP tools is to check whether the value is a password, or if
|
||||
# the first or last bytes are non-printable. Microsoft Active Directory,
|
||||
# on the other hand, sometimes sends values that are binary in the middle.
|
||||
#
|
||||
# In the worst cases, this could be a nasty performance killer, which is
|
||||
# why we handle the simplest cases first. Ideally, we would also test the
|
||||
# first/last byte, but it's a bit harder to do this in a way that's
|
||||
# compatible with both 1.8.6 and 1.8.7.
|
||||
def value_is_binary?(value) # :nodoc:
|
||||
value = value.to_s
|
||||
return true if value[0] == ?: or value[0] == ?<
|
||||
value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
|
||||
false
|
||||
end
|
||||
private :value_is_binary?
|
||||
|
||||
class << self
|
||||
class ChompedIO # :nodoc:
|
||||
def initialize(io)
|
||||
@io = io
|
||||
end
|
||||
def gets
|
||||
s = @io.gets
|
||||
s.chomp if s
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Dataset object from an Entry object. Used mostly to assist
|
||||
# with the conversion of
|
||||
def from_entry(entry)
|
||||
dataset = Net::LDAP::Dataset.new
|
||||
hash = { }
|
||||
entry.each_attribute do |attribute, value|
|
||||
next if attribute == :dn
|
||||
hash[attribute] = value
|
||||
end
|
||||
dataset[entry.dn] = hash
|
||||
dataset
|
||||
end
|
||||
|
||||
##
|
||||
# Reads an object that returns data line-wise (using #gets) and parses
|
||||
# LDIF data into a Dataset object.
|
||||
def read_ldif(io)
|
||||
ds = Net::LDAP::Dataset.new
|
||||
io = ChompedIO.new(io)
|
||||
|
||||
line = io.gets
|
||||
dn = nil
|
||||
|
||||
while line
|
||||
new_line = io.gets
|
||||
|
||||
if new_line =~ /^[\s]+/
|
||||
line << " " << $'
|
||||
else
|
||||
nextline = new_line
|
||||
|
||||
if line =~ /^#/
|
||||
ds.comments << line
|
||||
yield :comment, line if block_given?
|
||||
elsif line =~ /^dn:[\s]*/i
|
||||
dn = $'
|
||||
ds[dn] = Hash.new { |k,v| k[v] = [] }
|
||||
yield :dn, dn if block_given?
|
||||
elsif line.empty?
|
||||
dn = nil
|
||||
yield :end, nil if block_given?
|
||||
elsif line =~ /^([^:]+):([\:]?)[\s]*/
|
||||
# $1 is the attribute name
|
||||
# $2 is a colon iff the attr-value is base-64 encoded
|
||||
# $' is the attr-value
|
||||
# Avoid the Base64 class because not all Ruby versions have it.
|
||||
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
|
||||
ds[dn][$1.downcase.to_sym] << attrvalue
|
||||
yield :attr, [$1.downcase.to_sym, attrvalue] if block_given?
|
||||
end
|
||||
|
||||
line = nextline
|
||||
end
|
||||
end
|
||||
|
||||
ds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'net/ldap/entry' unless defined? Net::LDAP::Entry
|
||||
225
vendor/gems/net-ldap-0.2.2/lib/net/ldap/dn.rb
vendored
Normal file
225
vendor/gems/net-ldap-0.2.2/lib/net/ldap/dn.rb
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
|
||||
##
|
||||
# Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
|
||||
# ("Distinguished Name") is a unique identifier for an entry within an LDAP
|
||||
# directory. It is made up of a number of other attributes strung together,
|
||||
# to identify the entry in the tree.
|
||||
#
|
||||
# Each attribute that makes up a DN needs to have its value escaped so that
|
||||
# the DN is valid. This class helps take care of that.
|
||||
#
|
||||
# A fully escaped DN needs to be unescaped when analysing its contents. This
|
||||
# class also helps take care of that.
|
||||
class Net::LDAP::DN
|
||||
##
|
||||
# Initialize a DN, escaping as required. Pass in attributes in name/value
|
||||
# pairs. If there is a left over argument, it will be appended to the dn
|
||||
# without escaping (useful for a base string).
|
||||
#
|
||||
# Most uses of this class will be to escape a DN, rather than to parse it,
|
||||
# so storing the dn as an escaped String and parsing parts as required
|
||||
# with a state machine seems sensible.
|
||||
def initialize(*args)
|
||||
buffer = StringIO.new
|
||||
|
||||
args.each_index do |index|
|
||||
buffer << "=" if index % 2 == 1
|
||||
buffer << "," if index % 2 == 0 && index != 0
|
||||
|
||||
if index < args.length - 1 || index % 2 == 1
|
||||
buffer << Net::LDAP::DN.escape(args[index])
|
||||
else
|
||||
buffer << args[index]
|
||||
end
|
||||
end
|
||||
|
||||
@dn = buffer.string
|
||||
end
|
||||
|
||||
##
|
||||
# Parse a DN into key value pairs using ASN from
|
||||
# http://tools.ietf.org/html/rfc2253 section 3.
|
||||
def each_pair
|
||||
state = :key
|
||||
key = StringIO.new
|
||||
value = StringIO.new
|
||||
hex_buffer = ""
|
||||
|
||||
@dn.each_char do |char|
|
||||
case state
|
||||
when :key then
|
||||
case char
|
||||
when 'a'..'z', 'A'..'Z' then
|
||||
state = :key_normal
|
||||
key << char
|
||||
when '0'..'9' then
|
||||
state = :key_oid
|
||||
key << char
|
||||
when ' ' then state = :key
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :key_normal then
|
||||
case char
|
||||
when '=' then state = :value
|
||||
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :key_oid then
|
||||
case char
|
||||
when '=' then state = :value
|
||||
when '0'..'9', '.', ' ' then key << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value then
|
||||
case char
|
||||
when '\\' then state = :value_normal_escape
|
||||
when '"' then state = :value_quoted
|
||||
when ' ' then state = :value
|
||||
when '#' then
|
||||
state = :value_hexstring
|
||||
value << char
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else
|
||||
state = :value_normal
|
||||
value << char
|
||||
end
|
||||
when :value_normal then
|
||||
case char
|
||||
when '\\' then state = :value_normal_escape
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else value << char
|
||||
end
|
||||
when :value_normal_escape then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_normal_escape_hex
|
||||
hex_buffer = char
|
||||
else state = :value_normal; value << char
|
||||
end
|
||||
when :value_normal_escape_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_normal
|
||||
value << "#{hex_buffer}#{char}".to_i(16).chr
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_quoted then
|
||||
case char
|
||||
when '\\' then state = :value_quoted_escape
|
||||
when '"' then state = :value_end
|
||||
else value << char
|
||||
end
|
||||
when :value_quoted_escape then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_quoted_escape_hex
|
||||
hex_buffer = char
|
||||
else
|
||||
state = :value_quoted;
|
||||
value << char
|
||||
end
|
||||
when :value_quoted_escape_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_quoted
|
||||
value << "#{hex_buffer}#{char}".to_i(16).chr
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_hexstring then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_hexstring_hex
|
||||
value << char
|
||||
when ' ' then state = :value_end
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_hexstring_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_hexstring
|
||||
value << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_end then
|
||||
case char
|
||||
when ' ' then state = :value_end
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
else raise "Fell out of state machine"
|
||||
end
|
||||
end
|
||||
|
||||
# Last pair
|
||||
if [:value, :value_normal, :value_hexstring, :value_end].include? state
|
||||
yield key.string.strip, value.string.rstrip
|
||||
else
|
||||
raise "DN badly formed"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the DN as an array in the form expected by the constructor.
|
||||
def to_a
|
||||
a = []
|
||||
self.each_pair { |key, value| a << key << value }
|
||||
a
|
||||
end
|
||||
|
||||
##
|
||||
# Return the DN as an escaped string.
|
||||
def to_s
|
||||
@dn
|
||||
end
|
||||
|
||||
# http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
|
||||
# for dn values. All of the following must be escaped in any normal string
|
||||
# using a single backslash ('\') as escape.
|
||||
ESCAPES = {
|
||||
',' => ',',
|
||||
'+' => '+',
|
||||
'"' => '"',
|
||||
'\\' => '\\',
|
||||
'<' => '<',
|
||||
'>' => '>',
|
||||
';' => ';',
|
||||
}
|
||||
|
||||
# Compiled character class regexp using the keys from the above hash, and
|
||||
# checking for a space or # at the start, or space at the end, of the
|
||||
# string.
|
||||
ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
|
||||
ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
|
||||
"])")
|
||||
|
||||
##
|
||||
# Escape a string for use in a DN value
|
||||
def self.escape(string)
|
||||
string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
|
||||
end
|
||||
|
||||
##
|
||||
# Proxy all other requests to the string object, because a DN is mainly
|
||||
# used within the library as a string
|
||||
def method_missing(method, *args, &block)
|
||||
@dn.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
185
vendor/gems/net-ldap-0.2.2/lib/net/ldap/entry.rb
vendored
Normal file
185
vendor/gems/net-ldap-0.2.2/lib/net/ldap/entry.rb
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# Objects of this class represent individual entries in an LDAP directory.
|
||||
# User code generally does not instantiate this class. Net::LDAP#search
|
||||
# provides objects of this class to user code, either as block parameters or
|
||||
# as return values.
|
||||
#
|
||||
# In LDAP-land, an "entry" is a collection of attributes that are uniquely
|
||||
# and globally identified by a DN ("Distinguished Name"). Attributes are
|
||||
# identified by short, descriptive words or phrases. Although a directory is
|
||||
# free to implement any attribute name, most of them follow rigorous
|
||||
# standards so that the range of commonly-encountered attribute names is not
|
||||
# large.
|
||||
#
|
||||
# An attribute name is case-insensitive. Most directories also restrict the
|
||||
# range of characters allowed in attribute names. To simplify handling
|
||||
# attribute names, Net::LDAP::Entry internally converts them to a standard
|
||||
# format. Therefore, the methods which take attribute names can take Strings
|
||||
# or Symbols, and work correctly regardless of case or capitalization.
|
||||
#
|
||||
# An attribute consists of zero or more data items called <i>values.</i> An
|
||||
# entry is the combination of a unique DN, a set of attribute names, and a
|
||||
# (possibly-empty) array of values for each attribute.
|
||||
#
|
||||
# Class Net::LDAP::Entry provides convenience methods for dealing with LDAP
|
||||
# entries. In addition to the methods documented below, you may access
|
||||
# individual attributes of an entry simply by giving the attribute name as
|
||||
# the name of a method call. For example:
|
||||
#
|
||||
# ldap.search( ... ) do |entry|
|
||||
# puts "Common name: #{entry.cn}"
|
||||
# puts "Email addresses:"
|
||||
# entry.mail.each {|ma| puts ma}
|
||||
# end
|
||||
#
|
||||
# If you use this technique to access an attribute that is not present in a
|
||||
# particular Entry object, a NoMethodError exception will be raised.
|
||||
#
|
||||
#--
|
||||
# Ugly problem to fix someday: We key off the internal hash with a canonical
|
||||
# form of the attribute name: convert to a string, downcase, then take the
|
||||
# symbol. Unfortunately we do this in at least three places. Should do it in
|
||||
# ONE place.
|
||||
class Net::LDAP::Entry
|
||||
##
|
||||
# This constructor is not generally called by user code.
|
||||
def initialize(dn = nil) #:nodoc:
|
||||
@myhash = {}
|
||||
@myhash[:dn] = [dn]
|
||||
end
|
||||
|
||||
##
|
||||
# Use the LDIF format for Marshal serialization.
|
||||
def _dump(depth) #:nodoc:
|
||||
to_ldif
|
||||
end
|
||||
|
||||
##
|
||||
# Use the LDIF format for Marshal serialization.
|
||||
def self._load(entry) #:nodoc:
|
||||
from_single_ldif_string(entry)
|
||||
end
|
||||
|
||||
class << self
|
||||
##
|
||||
# Converts a single LDIF entry string into an Entry object. Useful for
|
||||
# Marshal serialization. If a string with multiple LDIF entries is
|
||||
# provided, an exception will be raised.
|
||||
def from_single_ldif_string(ldif)
|
||||
ds = Net::LDAP::Dataset.read_ldif(::StringIO.new(ldif))
|
||||
|
||||
return nil if ds.empty?
|
||||
|
||||
raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
|
||||
|
||||
entry = ds.to_entries.first
|
||||
|
||||
return nil if entry.dn.nil?
|
||||
entry
|
||||
end
|
||||
|
||||
##
|
||||
# Canonicalizes an LDAP attribute name as a \Symbol. The name is
|
||||
# lowercased and, if present, a trailing equals sign is removed.
|
||||
def attribute_name(name)
|
||||
name = name.to_s.downcase
|
||||
name = name[0..-2] if name[-1] == ?=
|
||||
name.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Sets or replaces the array of values for the provided attribute. The
|
||||
# attribute name is canonicalized prior to assignment.
|
||||
#
|
||||
# When an attribute is set using this, that attribute is now made
|
||||
# accessible through methods as well.
|
||||
#
|
||||
# entry = Net::LDAP::Entry.new("dc=com")
|
||||
# entry.foo # => NoMethodError
|
||||
# entry["foo"] = 12345 # => [12345]
|
||||
# entry.foo # => [12345]
|
||||
def []=(name, value)
|
||||
@myhash[self.class.attribute_name(name)] = Kernel::Array(value)
|
||||
end
|
||||
|
||||
##
|
||||
# Reads the array of values for the provided attribute. The attribute name
|
||||
# is canonicalized prior to reading. Returns an empty array if the
|
||||
# attribute does not exist.
|
||||
def [](name)
|
||||
name = self.class.attribute_name(name)
|
||||
@myhash[name] || []
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the first distinguished name (dn) of the Entry as a \String.
|
||||
def dn
|
||||
self[:dn].first.to_s
|
||||
end
|
||||
|
||||
##
|
||||
# Returns an array of the attribute names present in the Entry.
|
||||
def attribute_names
|
||||
@myhash.keys
|
||||
end
|
||||
|
||||
##
|
||||
# Accesses each of the attributes present in the Entry.
|
||||
#
|
||||
# Calls a user-supplied block with each attribute in turn, passing two
|
||||
# arguments to the block: a Symbol giving the name of the attribute, and a
|
||||
# (possibly empty) \Array of data values.
|
||||
def each # :yields: attribute-name, data-values-array
|
||||
if block_given?
|
||||
attribute_names.each {|a|
|
||||
attr_name,values = a,self[a]
|
||||
yield attr_name, values
|
||||
}
|
||||
end
|
||||
end
|
||||
alias_method :each_attribute, :each
|
||||
|
||||
##
|
||||
# Converts the Entry to an LDIF-formatted String
|
||||
def to_ldif
|
||||
Net::LDAP::Dataset.from_entry(self).to_ldif_string
|
||||
end
|
||||
|
||||
def respond_to?(sym) #:nodoc:
|
||||
return true if valid_attribute?(self.class.attribute_name(sym))
|
||||
return super
|
||||
end
|
||||
|
||||
def method_missing(sym, *args, &block) #:nodoc:
|
||||
name = self.class.attribute_name(sym)
|
||||
|
||||
if valid_attribute?(name )
|
||||
if setter?(sym) && args.size == 1
|
||||
value = args.first
|
||||
value = Array(value)
|
||||
self[name]= value
|
||||
return value
|
||||
elsif args.empty?
|
||||
return self[name]
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Given a valid attribute symbol, returns true.
|
||||
def valid_attribute?(attr_name)
|
||||
attribute_names.include?(attr_name)
|
||||
end
|
||||
private :valid_attribute?
|
||||
|
||||
# Returns true if the symbol ends with an equal sign.
|
||||
def setter?(sym)
|
||||
sym.to_s[-1] == ?=
|
||||
end
|
||||
private :setter?
|
||||
end # class Entry
|
||||
|
||||
require 'net/ldap/dataset' unless defined? Net::LDAP::Dataset
|
||||
759
vendor/gems/net-ldap-0.2.2/lib/net/ldap/filter.rb
vendored
Normal file
759
vendor/gems/net-ldap-0.2.2/lib/net/ldap/filter.rb
vendored
Normal file
@@ -0,0 +1,759 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
|
||||
##
|
||||
# Class Net::LDAP::Filter is used to constrain LDAP searches. An object of
|
||||
# this class is passed to Net::LDAP#search in the parameter :filter.
|
||||
#
|
||||
# Net::LDAP::Filter supports the complete set of search filters available in
|
||||
# LDAP, including conjunction, disjunction and negation (AND, OR, and NOT).
|
||||
# This class supplants the (infamous) RFC 2254 standard notation for
|
||||
# specifying LDAP search filters.
|
||||
#--
|
||||
# NOTE: This wording needs to change as we will be supporting LDAPv3 search
|
||||
# filter strings (RFC 4515).
|
||||
#++
|
||||
#
|
||||
# Here's how to code the familiar "objectclass is present" filter:
|
||||
# f = Net::LDAP::Filter.present("objectclass")
|
||||
#
|
||||
# The object returned by this code can be passed directly to the
|
||||
# <tt>:filter</tt> parameter of Net::LDAP#search.
|
||||
#
|
||||
# See the individual class and instance methods below for more examples.
|
||||
class Net::LDAP::Filter
|
||||
##
|
||||
# Known filter types.
|
||||
FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex ]
|
||||
|
||||
def initialize(op, left, right) #:nodoc:
|
||||
unless FilterTypes.include?(op)
|
||||
raise Net::LDAP::LdapError, "Invalid or unsupported operator #{op.inspect} in LDAP Filter."
|
||||
end
|
||||
@op = op
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
|
||||
class << self
|
||||
# We don't want filters created except using our custom constructors.
|
||||
private :new
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must either be present or match a particular string.
|
||||
#
|
||||
# Specifying that an attribute is 'present' means only directory entries
|
||||
# which contain a value for the particular attribute will be selected by
|
||||
# the filter. This is useful in case of optional attributes such as
|
||||
# <tt>mail.</tt> Presence is indicated by giving the value "*" in the
|
||||
# second parameter to #eq. This example selects only entries that have
|
||||
# one or more values for <tt>sAMAccountName:</tt>
|
||||
#
|
||||
# f = Net::LDAP::Filter.eq("sAMAccountName", "*")
|
||||
#
|
||||
# To match a particular range of values, pass a string as the second
|
||||
# parameter to #eq. The string may contain one or more "*" characters as
|
||||
# wildcards: these match zero or more occurrences of any character. Full
|
||||
# regular-expressions are <i>not</i> supported due to limitations in the
|
||||
# underlying LDAP protocol. This example selects any entry with a
|
||||
# <tt>mail</tt> 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
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating extensible comparison. This Filter
|
||||
# object is currently considered EXPERIMENTAL.
|
||||
#
|
||||
# sample_attributes = ['cn:fr', 'cn:fr.eq',
|
||||
# 'cn:1.3.6.1.4.1.42.2.27.9.4.49.1.3', 'cn:dn:fr', 'cn:dn:fr.eq']
|
||||
# attr = sample_attributes.first # Pick an extensible attribute
|
||||
# value = 'roberts'
|
||||
#
|
||||
# filter = "#{attr}:=#{value}" # Basic String Filter
|
||||
# filter = Net::LDAP::Filter.ex(attr, value) # Net::LDAP::Filter
|
||||
#
|
||||
# # Perform a search with the Extensible Match Filter
|
||||
# Net::LDAP.search(:filter => filter)
|
||||
#--
|
||||
# The LDIF required to support the above examples on the OpenDS LDAP
|
||||
# server:
|
||||
#
|
||||
# version: 1
|
||||
#
|
||||
# dn: dc=example,dc=com
|
||||
# objectClass: domain
|
||||
# objectClass: top
|
||||
# dc: example
|
||||
#
|
||||
# dn: ou=People,dc=example,dc=com
|
||||
# objectClass: organizationalUnit
|
||||
# objectClass: top
|
||||
# ou: People
|
||||
#
|
||||
# dn: uid=1,ou=People,dc=example,dc=com
|
||||
# objectClass: person
|
||||
# objectClass: organizationalPerson
|
||||
# objectClass: inetOrgPerson
|
||||
# objectClass: top
|
||||
# cn:: csO0YsOpcnRz
|
||||
# sn:: YsO0YiByw7Riw6lydHM=
|
||||
# givenName:: YsO0Yg==
|
||||
# uid: 1
|
||||
#
|
||||
# =Refs:
|
||||
# * http://www.ietf.org/rfc/rfc2251.txt
|
||||
# * http://www.novell.com/documentation/edir88/edir88/?page=/documentation/edir88/edir88/data/agazepd.html
|
||||
# * https://docs.opends.org/2.0/page/SearchingUsingInternationalCollationRules
|
||||
#++
|
||||
def ex(attribute, value)
|
||||
new(:ex, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# 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.
|
||||
def ge(attribute, value)
|
||||
new(:ge, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that a particular attribute value
|
||||
# is less than or equal to the specified value.
|
||||
def le(attribute, value)
|
||||
new(:le, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Joins two or more filters so that all conditions must be true. Calling
|
||||
# <tt>Filter.join(left, right)</tt> is the same as <tt>left &
|
||||
# right</tt>.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet both conditions above.
|
||||
# z = Net::LDAP::Filter.join(x, y)
|
||||
def join(left, right)
|
||||
new(:and, left, right)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a disjoint comparison between two or more filters. Selects
|
||||
# entries where either the left or right side are true. Calling
|
||||
# <tt>Filter.intersect(left, right)</tt> is the same as <tt>left |
|
||||
# right</tt>.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet either condition above.
|
||||
# z = x | y
|
||||
def intersect(left, right)
|
||||
new(:or, left, right)
|
||||
end
|
||||
|
||||
##
|
||||
# Negates a filter. Calling <tt>Fitler.negate(filter)</tt> i s the same
|
||||
# as <tt>~filter</tt>.
|
||||
#
|
||||
# # Selects only entries that do not have an <tt>objectclass</tt>
|
||||
# # attribute.
|
||||
# x = ~Net::LDAP::Filter.present("objectclass")
|
||||
def negate(filter)
|
||||
new(:not, filter, nil)
|
||||
end
|
||||
|
||||
##
|
||||
# This is a synonym for #eq(attribute, "*"). Also known as #present and
|
||||
# #pres.
|
||||
def present?(attribute)
|
||||
eq(attribute, "*")
|
||||
end
|
||||
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 = {
|
||||
"\0" => '00', # NUL = %x00 ; null character
|
||||
'*' => '2A', # ASTERISK = %x2A ; asterisk ("*")
|
||||
'(' => '28', # LPARENS = %x28 ; left parenthesis ("(")
|
||||
')' => '29', # RPARENS = %x29 ; right parenthesis (")")
|
||||
'\\' => '5C', # ESC = %x5C ; esc (or backslash) ("\")
|
||||
}
|
||||
# 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
|
||||
# LDAP searchRequest PDU. See also the comments under #to_ber, including
|
||||
# the grammar snippet from the RFC.
|
||||
#--
|
||||
# We're hardcoding the BER constants from the RFC. These should be
|
||||
# broken out insto constants.
|
||||
def parse_ber(ber)
|
||||
case ber.ber_identifier
|
||||
when 0xa0 # context-specific constructed 0, "and"
|
||||
ber.map { |b| parse_ber(b) }.inject { |memo, obj| memo & obj }
|
||||
when 0xa1 # context-specific constructed 1, "or"
|
||||
ber.map { |b| parse_ber(b) }.inject { |memo, obj| memo | obj }
|
||||
when 0xa2 # context-specific constructed 2, "not"
|
||||
~parse_ber(ber.first)
|
||||
when 0xa3 # context-specific constructed 3, "equalityMatch"
|
||||
if ber.last == "*"
|
||||
else
|
||||
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 Net::LDAP::LdapError, "Unrecognized substring filter; bad initial value." 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
|
||||
eq(ber.first.to_s, str)
|
||||
when 0xa5 # context-specific constructed 5, "greaterOrEqual"
|
||||
ge(ber.first.to_s, ber.last.to_s)
|
||||
when 0xa6 # context-specific constructed 6, "lessOrEqual"
|
||||
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.
|
||||
present?(ber.to_s)
|
||||
when 0xa9 # context-specific constructed 9, "extensible comparison"
|
||||
raise Net::LDAP::LdapError, "Invalid extensible search filter, should be at least two elements" if ber.size<2
|
||||
|
||||
# Reassembles the extensible filter parts
|
||||
# (["sn", "2.4.6.8.10", "Barbara Jones", '1'])
|
||||
type = value = dn = rule = nil
|
||||
ber.each do |element|
|
||||
case element.ber_identifier
|
||||
when 0x81 then rule=element
|
||||
when 0x82 then type=element
|
||||
when 0x83 then value=element
|
||||
when 0x84 then dn='dn'
|
||||
end
|
||||
end
|
||||
|
||||
attribute = ''
|
||||
attribute << type if type
|
||||
attribute << ":#{dn}" if dn
|
||||
attribute << ":#{rule}" if rule
|
||||
|
||||
ex(attribute, value)
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Invalid BER tag-value (#{ber.ber_identifier}) in search filter."
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
|
||||
# to a Net::LDAP::Filter.
|
||||
def construct(ldap_filter_string)
|
||||
FilterParser.parse(ldap_filter_string)
|
||||
end
|
||||
alias_method :from_rfc2254, :construct
|
||||
alias_method :from_rfc4515, :construct
|
||||
|
||||
##
|
||||
# Convert an RFC-1777 LDAP/BER "Filter" object to a Net::LDAP::Filter
|
||||
# object.
|
||||
#--
|
||||
# TODO, we're hardcoding the RFC-1777 BER-encodings of the various
|
||||
# filter types. Could pull them out into a constant.
|
||||
#++
|
||||
def parse_ldap_filter(obj)
|
||||
case obj.ber_identifier
|
||||
when 0x87 # present. context-specific primitive 7.
|
||||
eq(obj.to_s, "*")
|
||||
when 0xa3 # equalityMatch. context-specific constructed 3.
|
||||
eq(obj[0], obj[1])
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unknown LDAP search-filter type: #{obj.ber_identifier}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Joins two or more filters so that all conditions must be true.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet both conditions above.
|
||||
# z = x & y
|
||||
def &(filter)
|
||||
self.class.join(self, filter)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a disjoint comparison between two or more filters. Selects
|
||||
# entries where either the left or right side are true.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet either condition above.
|
||||
# z = x | y
|
||||
def |(filter)
|
||||
self.class.intersect(self, filter)
|
||||
end
|
||||
|
||||
##
|
||||
# Negates a filter.
|
||||
#
|
||||
# # Selects only entries that do not have an <tt>objectclass</tt>
|
||||
# # attribute.
|
||||
# x = ~Net::LDAP::Filter.present("objectclass")
|
||||
def ~@
|
||||
self.class.negate(self)
|
||||
end
|
||||
|
||||
##
|
||||
# Equality operator for filters, useful primarily for constructing unit tests.
|
||||
def ==(filter)
|
||||
# 20100320 AZ: We need to come up with a better way of doing this. This
|
||||
# is just nasty.
|
||||
str = "[@op,@left,@right]"
|
||||
self.instance_eval(str) == filter.instance_eval(str)
|
||||
end
|
||||
|
||||
def to_raw_rfc2254
|
||||
case @op
|
||||
when :ne
|
||||
"!(#{@left}=#{@right})"
|
||||
when :eq
|
||||
"#{@left}=#{@right}"
|
||||
when :ex
|
||||
"#{@left}:=#{@right}"
|
||||
when :ge
|
||||
"#{@left}>=#{@right}"
|
||||
when :le
|
||||
"#{@left}<=#{@right}"
|
||||
when :and
|
||||
"&(#{@left.to_raw_rfc2254})(#{@right.to_raw_rfc2254})"
|
||||
when :or
|
||||
"|(#{@left.to_raw_rfc2254})(#{@right.to_raw_rfc2254})"
|
||||
when :not
|
||||
"!(#{@left.to_raw_rfc2254})"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the Filter object to an RFC 2254-compatible text format.
|
||||
def to_rfc2254
|
||||
"(#{to_raw_rfc2254})"
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_rfc2254
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the filter to BER format.
|
||||
#--
|
||||
# Filter ::=
|
||||
# CHOICE {
|
||||
# and [0] SET OF Filter,
|
||||
# or [1] SET OF Filter,
|
||||
# not [2] Filter,
|
||||
# equalityMatch [3] AttributeValueAssertion,
|
||||
# substrings [4] SubstringFilter,
|
||||
# greaterOrEqual [5] AttributeValueAssertion,
|
||||
# lessOrEqual [6] AttributeValueAssertion,
|
||||
# present [7] AttributeType,
|
||||
# approxMatch [8] AttributeValueAssertion,
|
||||
# extensibleMatch [9] MatchingRuleAssertion
|
||||
# }
|
||||
#
|
||||
# SubstringFilter ::=
|
||||
# SEQUENCE {
|
||||
# type AttributeType,
|
||||
# SEQUENCE OF CHOICE {
|
||||
# initial [0] LDAPString,
|
||||
# any [1] LDAPString,
|
||||
# final [2] LDAPString
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# MatchingRuleAssertion ::=
|
||||
# SEQUENCE {
|
||||
# matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
# type [2] AttributeDescription OPTIONAL,
|
||||
# matchValue [3] AssertionValue,
|
||||
# dnAttributes [4] BOOLEAN DEFAULT FALSE
|
||||
# }
|
||||
#
|
||||
# Matching Rule Suffixes
|
||||
# Less than [.1] or .[lt]
|
||||
# Less than or equal to [.2] or [.lte]
|
||||
# Equality [.3] or [.eq] (default)
|
||||
# Greater than or equal to [.4] or [.gte]
|
||||
# Greater than [.5] or [.gt]
|
||||
# Substring [.6] or [.sub]
|
||||
#
|
||||
#++
|
||||
def to_ber
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*" # presence test
|
||||
@left.to_s.to_ber_contextspecific(7)
|
||||
elsif @right =~ /[*]/ # substring
|
||||
# Parsing substrings is a little tricky. We use String#split to
|
||||
# break a string into substrings delimited by the * (star)
|
||||
# character. But we also need to know whether there is a star at the
|
||||
# head and tail of the string, so we use a limit parameter value of
|
||||
# -1: "If negative, there is no limit to the number of fields
|
||||
# returned, and trailing null fields are not suppressed."
|
||||
#
|
||||
# 20100320 AZ: This is much simpler than the previous verison. Also,
|
||||
# unnecessary regex escaping has been removed.
|
||||
|
||||
ary = @right.split(/[*]+/, -1)
|
||||
|
||||
if ary.first.empty?
|
||||
first = nil
|
||||
ary.shift
|
||||
else
|
||||
first = ary.shift.to_ber_contextspecific(0)
|
||||
end
|
||||
|
||||
if ary.last.empty?
|
||||
last = nil
|
||||
ary.pop
|
||||
else
|
||||
last = ary.pop.to_ber_contextspecific(2)
|
||||
end
|
||||
|
||||
seq = ary.map { |e| e.to_ber_contextspecific(1) }
|
||||
seq.unshift first if first
|
||||
seq.push last if last
|
||||
|
||||
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific(4)
|
||||
else # equality
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(3)
|
||||
end
|
||||
when :ex
|
||||
seq = []
|
||||
|
||||
unless @left =~ /^([-;\w]*)(:dn)?(:(\w+|[.\w]+))?$/
|
||||
raise Net::LDAP::LdapError, "Bad attribute #{@left}"
|
||||
end
|
||||
type, dn, rule = $1, $2, $4
|
||||
|
||||
seq << rule.to_ber_contextspecific(1) unless rule.to_s.empty? # matchingRule
|
||||
seq << type.to_ber_contextspecific(2) unless type.to_s.empty? # type
|
||||
seq << unescape(@right).to_ber_contextspecific(3) # matchingValue
|
||||
seq << "1".to_ber_contextspecific(4) unless dn.to_s.empty? # dnAttributes
|
||||
|
||||
seq.to_ber_contextspecific(9)
|
||||
when :ge
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(5)
|
||||
when :le
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(6)
|
||||
when :ne
|
||||
[self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2)
|
||||
when :and
|
||||
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific(0)
|
||||
when :or
|
||||
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific(1)
|
||||
when :not
|
||||
[@left.to_ber].to_ber_contextspecific(2)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Perform filter operations against a user-supplied block. This is useful
|
||||
# when implementing an LDAP directory server. The caller's block will be
|
||||
# called with two arguments: first, a symbol denoting the "operation" of
|
||||
# the filter; and second, an array consisting of arguments to the
|
||||
# operation. The user-supplied block (which is MANDATORY) should perform
|
||||
# some desired application-defined processing, and may return a
|
||||
# locally-meaningful object that will appear as a parameter in the :and,
|
||||
# :or and :not operations detailed below.
|
||||
#
|
||||
# A typical object to return from the user-supplied block is an array of
|
||||
# Net::LDAP::Filter objects.
|
||||
#
|
||||
# These are the possible values that may be passed to the user-supplied
|
||||
# block:
|
||||
# * :equalityMatch (the arguments will be an attribute name and a value
|
||||
# to be matched);
|
||||
# * :substrings (two arguments: an attribute name and a value containing
|
||||
# one or more "*" characters);
|
||||
# * :present (one argument: an attribute name);
|
||||
# * :greaterOrEqual (two arguments: an attribute name and a value to be
|
||||
# compared against);
|
||||
# * :lessOrEqual (two arguments: an attribute name and a value to be
|
||||
# compared against);
|
||||
# * :and (two or more arguments, each of which is an object returned
|
||||
# from a recursive call to #execute, with the same block;
|
||||
# * :or (two or more arguments, each of which is an object returned from
|
||||
# a recursive call to #execute, with the same block; and
|
||||
# * :not (one argument, which is an object returned from a recursive
|
||||
# call to #execute with the the same block.
|
||||
def execute(&block)
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*"
|
||||
yield :present, @left
|
||||
elsif @right.index '*'
|
||||
yield :substrings, @left, @right
|
||||
else
|
||||
yield :equalityMatch, @left, @right
|
||||
end
|
||||
when :ge
|
||||
yield :greaterOrEqual, @left, @right
|
||||
when :le
|
||||
yield :lessOrEqual, @left, @right
|
||||
when :or, :and
|
||||
yield @op, (@left.execute(&block)), (@right.execute(&block))
|
||||
when :not
|
||||
yield @op, (@left.execute(&block))
|
||||
end || []
|
||||
end
|
||||
|
||||
##
|
||||
# This is a private helper method for dealing with chains of ANDs and ORs
|
||||
# that are longer than two. If BOTH of our branches are of the specified
|
||||
# type of joining operator, then return both of them as an array (calling
|
||||
# coalesce recursively). If they're not, then return an array consisting
|
||||
# only of self.
|
||||
def coalesce(operator) #:nodoc:
|
||||
if @op == operator
|
||||
[@left.coalesce(operator), @right.coalesce(operator)]
|
||||
else
|
||||
[self]
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#--
|
||||
# We got a hash of attribute values.
|
||||
# Do we match the attributes?
|
||||
# Return T/F, and call match recursively as necessary.
|
||||
#++
|
||||
def match(entry)
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*"
|
||||
l = entry[@left] and l.length > 0
|
||||
else
|
||||
l = entry[@left] and l = Array(l) and l.index(@right)
|
||||
end
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unknown filter type in match: #{@op}"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts escaped characters (e.g., "\\28") to unescaped characters
|
||||
# ("(").
|
||||
def unescape(right)
|
||||
right.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
|
||||
end
|
||||
private :unescape
|
||||
|
||||
##
|
||||
# Parses RFC 2254-style string representations of LDAP filters into Filter
|
||||
# object hierarchies.
|
||||
class FilterParser #:nodoc:
|
||||
##
|
||||
# The constructed filter.
|
||||
attr_reader :filter
|
||||
|
||||
class << self
|
||||
private :new
|
||||
|
||||
##
|
||||
# Construct a filter tree from the provided string and return it.
|
||||
def parse(ldap_filter_string)
|
||||
new(ldap_filter_string).filter
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(str)
|
||||
require 'strscan' # Don't load strscan until we need it.
|
||||
@filter = parse(StringScanner.new(str))
|
||||
raise Net::LDAP::LdapError, "Invalid filter syntax." unless @filter
|
||||
end
|
||||
|
||||
##
|
||||
# Parse the string contained in the StringScanner provided. Parsing
|
||||
# tries to parse a standalone expression first. If that fails, it tries
|
||||
# to parse a parenthesized expression.
|
||||
def parse(scanner)
|
||||
parse_filter_branch(scanner) or parse_paren_expression(scanner)
|
||||
end
|
||||
private :parse
|
||||
|
||||
##
|
||||
# Join ("&") and intersect ("|") operations are presented in branches.
|
||||
# That is, the expression <tt>(&(test1)(test2)</tt> has two branches:
|
||||
# test1 and test2. Each of these is parsed separately and then pushed
|
||||
# into a branch array for filter merging using the parent operation.
|
||||
#
|
||||
# This method parses the branch text out into an array of filter
|
||||
# objects.
|
||||
def parse_branches(scanner)
|
||||
branches = []
|
||||
while branch = parse_paren_expression(scanner)
|
||||
branches << branch
|
||||
end
|
||||
branches
|
||||
end
|
||||
private :parse_branches
|
||||
|
||||
##
|
||||
# Join ("&") and intersect ("|") operations are presented in branches.
|
||||
# That is, the expression <tt>(&(test1)(test2)</tt> has two branches:
|
||||
# test1 and test2. Each of these is parsed separately and then pushed
|
||||
# into a branch array for filter merging using the parent operation.
|
||||
#
|
||||
# This method calls #parse_branches to generate the branch list and then
|
||||
# merges them into a single Filter tree by calling the provided
|
||||
# operation.
|
||||
def merge_branches(op, scanner)
|
||||
filter = nil
|
||||
branches = parse_branches(scanner)
|
||||
|
||||
if branches.size >= 1
|
||||
filter = branches.shift
|
||||
while not branches.empty?
|
||||
filter = filter.__send__(op, branches.shift)
|
||||
end
|
||||
end
|
||||
|
||||
filter
|
||||
end
|
||||
private :merge_branches
|
||||
|
||||
def parse_paren_expression(scanner)
|
||||
if scanner.scan(/\s*\(\s*/)
|
||||
expr = if scanner.scan(/\s*\&\s*/)
|
||||
merge_branches(:&, scanner)
|
||||
elsif scanner.scan(/\s*\|\s*/)
|
||||
merge_branches(:|, scanner)
|
||||
elsif scanner.scan(/\s*\!\s*/)
|
||||
br = parse_paren_expression(scanner)
|
||||
~br if br
|
||||
else
|
||||
parse_filter_branch(scanner)
|
||||
end
|
||||
|
||||
if expr and scanner.scan(/\s*\)\s*/)
|
||||
expr
|
||||
end
|
||||
end
|
||||
end
|
||||
private :parse_paren_expression
|
||||
|
||||
##
|
||||
# This parses a given expression inside of parentheses.
|
||||
def parse_filter_branch(scanner)
|
||||
scanner.scan(/\s*/)
|
||||
if token = scanner.scan(/[-\w:.]*[\w]/)
|
||||
scanner.scan(/\s*/)
|
||||
if op = scanner.scan(/<=|>=|!=|:=|=/)
|
||||
scanner.scan(/\s*/)
|
||||
if value = scanner.scan(/(?:[-\w*.+@=,#\$%&!'\s]|\\[a-fA-F\d]{2})+/)
|
||||
# 20100313 AZ: Assumes that "(uid=george*)" is the same as
|
||||
# "(uid=george* )". The standard doesn't specify, but I can find
|
||||
# no examples that suggest otherwise.
|
||||
value.strip!
|
||||
case op
|
||||
when "="
|
||||
Net::LDAP::Filter.eq(token, value)
|
||||
when "!="
|
||||
Net::LDAP::Filter.ne(token, value)
|
||||
when "<="
|
||||
Net::LDAP::Filter.le(token, value)
|
||||
when ">="
|
||||
Net::LDAP::Filter.ge(token, value)
|
||||
when ":="
|
||||
Net::LDAP::Filter.ex(token, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :parse_filter_branch
|
||||
end # class Net::LDAP::FilterParser
|
||||
end # class Net::LDAP::Filter
|
||||
31
vendor/gems/net-ldap-0.2.2/lib/net/ldap/password.rb
vendored
Normal file
31
vendor/gems/net-ldap-0.2.2/lib/net/ldap/password.rb
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'digest/sha1'
|
||||
require 'digest/md5'
|
||||
|
||||
class Net::LDAP::Password
|
||||
class << self
|
||||
# Generate a password-hash suitable for inclusion in an LDAP attribute.
|
||||
# Pass a hash type (currently supported: :md5 and :sha) and a plaintext
|
||||
# password. This function will return a hashed representation.
|
||||
#
|
||||
#--
|
||||
# STUB: This is here to fulfill the requirements of an RFC, which
|
||||
# one?
|
||||
#
|
||||
# TODO, gotta do salted-sha and (maybe)salted-md5. Should we provide
|
||||
# sha1 as a synonym for sha1? I vote no because then should you also
|
||||
# provide ssha1 for symmetry?
|
||||
def generate(type, str)
|
||||
digest, digest_name = case type
|
||||
when :md5
|
||||
[Digest::MD5.new, 'MD5']
|
||||
when :sha
|
||||
[Digest::SHA1.new, 'SHA']
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unsupported password-hash type (#{type})"
|
||||
end
|
||||
digest << str.to_s
|
||||
return "{#{digest_name}}#{[digest.digest].pack('m').chomp }"
|
||||
end
|
||||
end
|
||||
end
|
||||
256
vendor/gems/net-ldap-0.2.2/lib/net/ldap/pdu.rb
vendored
Normal file
256
vendor/gems/net-ldap-0.2.2/lib/net/ldap/pdu.rb
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'ostruct'
|
||||
|
||||
##
|
||||
# Defines the Protocol Data Unit (PDU) for LDAP. An LDAP PDU always looks
|
||||
# like a BER SEQUENCE with at least two elements: an INTEGER message ID
|
||||
# number and an application-specific SEQUENCE. Some LDAPv3 packets also
|
||||
# include an optional third element, a sequence of "controls" (see RFC 2251
|
||||
# section 4.1.12 for more information).
|
||||
#
|
||||
# The application-specific tag in the sequence tells us what kind of packet
|
||||
# it is, and each kind has its own format, defined in RFC-1777.
|
||||
#
|
||||
# Observe that many clients (such as ldapsearch) do not necessarily enforce
|
||||
# the expected application tags on received protocol packets. This
|
||||
# implementation does interpret the RFC strictly in this regard, and it
|
||||
# remains to be seen whether there are servers out there that will not work
|
||||
# well with our approach.
|
||||
#
|
||||
# Currently, we only support controls on SearchResult.
|
||||
class Net::LDAP::PDU
|
||||
class Error < RuntimeError; end
|
||||
|
||||
##
|
||||
# This message packet is a bind request.
|
||||
BindRequest = 0
|
||||
BindResult = 1
|
||||
UnbindRequest = 2
|
||||
SearchRequest = 3
|
||||
SearchReturnedData = 4
|
||||
SearchResult = 5
|
||||
ModifyResponse = 7
|
||||
AddResponse = 9
|
||||
DeleteResponse = 11
|
||||
ModifyRDNResponse = 13
|
||||
SearchResultReferral = 19
|
||||
ExtendedRequest = 23
|
||||
ExtendedResponse = 24
|
||||
|
||||
##
|
||||
# The LDAP packet message ID.
|
||||
attr_reader :message_id
|
||||
alias_method :msg_id, :message_id
|
||||
|
||||
##
|
||||
# The application protocol format tag.
|
||||
attr_reader :app_tag
|
||||
|
||||
attr_reader :search_entry
|
||||
attr_reader :search_referrals
|
||||
attr_reader :search_parameters
|
||||
attr_reader :bind_parameters
|
||||
|
||||
##
|
||||
# Returns RFC-2251 Controls if any.
|
||||
attr_reader :ldap_controls
|
||||
alias_method :result_controls, :ldap_controls
|
||||
# Messy. Does this functionality belong somewhere else?
|
||||
|
||||
def initialize(ber_object)
|
||||
begin
|
||||
@message_id = ber_object[0].to_i
|
||||
# Grab the bottom five bits of the identifier so we know which type of
|
||||
# PDU this is.
|
||||
#
|
||||
# This is safe enough in LDAP-land, but it is recommended that other
|
||||
# approaches be taken for other protocols in the case that there's an
|
||||
# app-specific tag that has both primitive and constructed forms.
|
||||
@app_tag = ber_object[1].ber_identifier & 0x1f
|
||||
@ldap_controls = []
|
||||
rescue Exception => ex
|
||||
raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
|
||||
end
|
||||
|
||||
case @app_tag
|
||||
when BindResult
|
||||
parse_bind_response(ber_object[1])
|
||||
when SearchReturnedData
|
||||
parse_search_return(ber_object[1])
|
||||
when SearchResultReferral
|
||||
parse_search_referral(ber_object[1])
|
||||
when SearchResult
|
||||
parse_ldap_result(ber_object[1])
|
||||
when ModifyResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when AddResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when DeleteResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when ModifyRDNResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
when SearchRequest
|
||||
parse_ldap_search_request(ber_object[1])
|
||||
when BindRequest
|
||||
parse_bind_request(ber_object[1])
|
||||
when UnbindRequest
|
||||
parse_unbind_request(ber_object[1])
|
||||
when ExtendedResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
else
|
||||
raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
|
||||
end
|
||||
|
||||
parse_controls(ber_object[2]) if ber_object[2]
|
||||
end
|
||||
|
||||
##
|
||||
# Returns a hash which (usually) defines the members :resultCode,
|
||||
# :errorMessage, and :matchedDN. These values come directly from an LDAP
|
||||
# response packet returned by the remote peer. Also see #result_code.
|
||||
def result
|
||||
@ldap_result || {}
|
||||
end
|
||||
|
||||
##
|
||||
# This returns an LDAP result code taken from the PDU, but it will be nil
|
||||
# if there wasn't a result code. That can easily happen depending on the
|
||||
# type of packet.
|
||||
def result_code(code = :resultCode)
|
||||
@ldap_result and @ldap_result[code]
|
||||
end
|
||||
|
||||
##
|
||||
# Return serverSaslCreds, which are only present in BindResponse packets.
|
||||
#--
|
||||
# Messy. Does this functionality belong somewhere else? We ought to
|
||||
# refactor the accessors of this class before they get any kludgier.
|
||||
def result_server_sasl_creds
|
||||
@ldap_result && @ldap_result[:serverSaslCreds]
|
||||
end
|
||||
|
||||
def parse_ldap_result(sequence)
|
||||
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
|
||||
@ldap_result = {
|
||||
:resultCode => sequence[0],
|
||||
:matchedDN => sequence[1],
|
||||
:errorMessage => sequence[2]
|
||||
}
|
||||
end
|
||||
private :parse_ldap_result
|
||||
|
||||
##
|
||||
# A Bind Response may have an additional field, ID [7], serverSaslCreds,
|
||||
# per RFC 2251 pgh 4.2.3.
|
||||
def parse_bind_response(sequence)
|
||||
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP Bind Response length."
|
||||
parse_ldap_result(sequence)
|
||||
@ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
|
||||
@ldap_result
|
||||
end
|
||||
private :parse_bind_response
|
||||
|
||||
# Definition from RFC 1777 (we're handling application-4 here).
|
||||
#
|
||||
# Search Response ::=
|
||||
# CHOICE {
|
||||
# entry [APPLICATION 4] SEQUENCE {
|
||||
# objectName LDAPDN,
|
||||
# attributes SEQUENCE OF SEQUENCE {
|
||||
# AttributeType,
|
||||
# SET OF AttributeValue
|
||||
# }
|
||||
# },
|
||||
# resultCode [APPLICATION 5] LDAPResult
|
||||
# }
|
||||
#
|
||||
# We concoct a search response that is a hash of the returned attribute
|
||||
# values.
|
||||
#
|
||||
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
|
||||
#
|
||||
# This is to make them more predictable for user programs, but it may not
|
||||
# be a good idea. Maybe this should be configurable.
|
||||
def parse_search_return(sequence)
|
||||
sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
|
||||
@search_entry = Net::LDAP::Entry.new(sequence[0])
|
||||
sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
|
||||
end
|
||||
private :parse_search_return
|
||||
|
||||
##
|
||||
# A search referral is a sequence of one or more LDAP URIs. Any number of
|
||||
# search-referral replies can be returned by the server, interspersed with
|
||||
# normal replies in any order.
|
||||
#--
|
||||
# Until I can think of a better way to do this, we'll return the referrals
|
||||
# as an array. It'll be up to higher-level handlers to expose something
|
||||
# reasonable to the client.
|
||||
def parse_search_referral(uris)
|
||||
@search_referrals = uris
|
||||
end
|
||||
private :parse_search_referral
|
||||
|
||||
##
|
||||
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
|
||||
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
|
||||
# Octet String. If only two fields are given, the second one may be either
|
||||
# criticality or data, since criticality has a default value. Someday we
|
||||
# may want to come back here and add support for some of more-widely used
|
||||
# controls. RFC-2696 is a good example.
|
||||
def parse_controls(sequence)
|
||||
@ldap_controls = sequence.map do |control|
|
||||
o = OpenStruct.new
|
||||
o.oid, o.criticality, o.value = control[0], control[1], control[2]
|
||||
if o.criticality and o.criticality.is_a?(String)
|
||||
o.value = o.criticality
|
||||
o.criticality = false
|
||||
end
|
||||
o
|
||||
end
|
||||
end
|
||||
private :parse_controls
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_ldap_search_request(sequence)
|
||||
s = OpenStruct.new
|
||||
s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
|
||||
s.types_only, s.filter, s.attributes = sequence
|
||||
@search_parameters = s
|
||||
end
|
||||
private :parse_ldap_search_request
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_bind_request sequence
|
||||
s = OpenStruct.new
|
||||
s.version, s.name, s.authentication = sequence
|
||||
@bind_parameters = s
|
||||
end
|
||||
private :parse_bind_request
|
||||
|
||||
# (provisional, must document)
|
||||
# UnbindRequest has no content so this is a no-op.
|
||||
def parse_unbind_request(sequence)
|
||||
nil
|
||||
end
|
||||
private :parse_unbind_request
|
||||
end
|
||||
|
||||
module Net
|
||||
##
|
||||
# Handle renamed constants Net::LdapPdu (Net::LDAP::PDU) and
|
||||
# Net::LdapPduError (Net::LDAP::PDU::Error).
|
||||
def self.const_missing(name) #:nodoc:
|
||||
case name.to_s
|
||||
when "LdapPdu"
|
||||
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU instead."
|
||||
Net::LDAP::PDU
|
||||
when "LdapPduError"
|
||||
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU::Error instead."
|
||||
Net::LDAP::PDU::Error
|
||||
when 'LDAP'
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end # module Net
|
||||
Reference in New Issue
Block a user