You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
8.6 KiB
283 lines
8.6 KiB
require 'ldap/server/syntax'
|
|
require 'ldap/server/result'
|
|
|
|
module LDAP
|
|
class Server
|
|
|
|
# A class which holds LDAP MatchingRules. For now there is a global pool
|
|
# of MatchingRule objects (rather than each Schema object having
|
|
# its own pool)
|
|
|
|
class MatchingRule
|
|
attr_reader :oid, :names, :syntax, :desc, :obsolete
|
|
|
|
# Create a new MatchingRule object
|
|
|
|
def initialize(oid, names, syntax, desc=nil, obsolete=false, &blk)
|
|
@oid = oid
|
|
@names = names
|
|
@names = [@names] unless @names.is_a?(Array)
|
|
@desc = desc
|
|
@obsolete = obsolete
|
|
@syntax = LDAP::Server::Syntax.find(syntax) # creates new obj if reqd
|
|
@def = nil
|
|
# initialization hook
|
|
self.instance_eval(&blk) if blk
|
|
end
|
|
|
|
def name
|
|
(@names && names[0]) || @oid
|
|
end
|
|
|
|
def to_s
|
|
(@names && names[0]) || @oid
|
|
end
|
|
|
|
def normalize(x)
|
|
x
|
|
end
|
|
|
|
# Create a new MatchingRule object, given its description string
|
|
|
|
def self.from_def(str, &blk)
|
|
m = LDAP::Server::Syntax::MatchingRuleDescription.match(str)
|
|
raise LDAP::ResultError::InvalidAttributeSyntax,
|
|
"Bad MatchingRuleDescription #{str.inspect}" unless m
|
|
new(m[1], m[2].scan(/'(.*?)'/).flatten, m[5], m[3], m[4], &blk)
|
|
end
|
|
|
|
def to_def
|
|
return @def if @def
|
|
ans = "( #{@oid} "
|
|
if names.nil? or @names.empty?
|
|
# nothing
|
|
elsif @names.size == 1
|
|
ans << "NAME '#{@names[0]}' "
|
|
else
|
|
ans << "NAME ( "
|
|
@names.each { |n| ans << "'#{n}' " }
|
|
ans << ") "
|
|
end
|
|
ans << "DESC '#@desc' " if @desc
|
|
ans << "OBSOLETE " if @obsolete
|
|
ans << "SYNTAX #@syntax " if @syntax
|
|
ans << ")"
|
|
@def = ans
|
|
end
|
|
|
|
@@rules = {} # oid / name / alias => object
|
|
|
|
# Add a new matching rule
|
|
|
|
def self.add(*args, &blk)
|
|
s = new(*args, &blk)
|
|
@@rules[s.oid] = s
|
|
return if s.names.nil?
|
|
s.names.each do |n|
|
|
@@rules[n.downcase] = s
|
|
end
|
|
end
|
|
|
|
# Find a MatchingRule object given a name or oid, or return nil
|
|
# (? should we create one automatically, like Syntax)
|
|
|
|
def self.find(x)
|
|
return x if x.nil? or x.is_a?(LDAP::Server::MatchingRule)
|
|
@@rules[x.downcase]
|
|
end
|
|
|
|
# Return all known matching rules
|
|
|
|
def self.all_matching_rules
|
|
@@rules.values.uniq
|
|
end
|
|
|
|
# Now some things we can mixin to a MatchingRule when needed.
|
|
# Replace 'normalize' with a function which gives the canonical
|
|
# version of a value for comparison.
|
|
|
|
module Equality
|
|
def eq(vals, m)
|
|
return false if vals.nil?
|
|
m = normalize(m)
|
|
vals.each { |v| return true if normalize(v) == m }
|
|
return false
|
|
end
|
|
end
|
|
|
|
module Ordering
|
|
def ge(vals, m)
|
|
return false if vals.nil?
|
|
m = normalize(m)
|
|
vals.each { |v| return true if normalize(v) >= m }
|
|
return false
|
|
end
|
|
|
|
def le(vals, m)
|
|
return false if vals.nil?
|
|
m = normalize(m)
|
|
vals.each { |v| return true if normalize(v) <= m }
|
|
return false
|
|
end
|
|
end
|
|
|
|
module Substrings
|
|
def substrings(vals, *ss)
|
|
return false if vals.nil?
|
|
|
|
# convert the condition list into a regexp
|
|
re = []
|
|
re << "^#{Regexp.escape(normalize(ss[0]).to_s)}" if ss[0]
|
|
ss[1..-2].each { |s| re << Regexp.escape(normalize(s).to_s) }
|
|
re << "#{Regexp.escape(normalize(ss[-1]).to_s)}$" if ss[-1]
|
|
re = Regexp.new(re.join(".*"))
|
|
|
|
vals.each do |v|
|
|
v = normalize(v).to_s
|
|
return true if re.match(v)
|
|
end
|
|
return false
|
|
end
|
|
end # module Substrings
|
|
|
|
class DefaultMatchingClass
|
|
include MatchingRule::Equality
|
|
include MatchingRule::Ordering
|
|
include MatchingRule::Substrings
|
|
def normalize(x)
|
|
x
|
|
end
|
|
end
|
|
|
|
DefaultMatch = DefaultMatchingClass.new
|
|
|
|
end # class MatchingRule
|
|
|
|
#
|
|
# And now, here are some matching rules you can use (RFC2252 section 8)
|
|
#
|
|
|
|
class MatchingRule
|
|
|
|
add('2.5.13.0', 'objectIdentifierMatch', '1.3.6.1.4.1.1466.115.121.1.38') do
|
|
extend Equality
|
|
end
|
|
# FIXME: Filters should return undef if the OID is not in the schema
|
|
# (which means passing in the schema to every equality test)
|
|
|
|
add('2.5.13.1', 'distinguishedNameMatch', '1.3.6.1.4.1.1466.115.121.1.12') do
|
|
extend Equality
|
|
end
|
|
# FIXME: Distinguished Name matching is supposed to parse the DN into
|
|
# its parts and then apply the schema equality rules to each part
|
|
# (i.e. some parts may be case-sensitive, others case-insensitive)
|
|
# This is just one of the many nonsense design decisions in LDAP :-(
|
|
|
|
# How is a DirectoryString different to an IA5String or a PrintableString?
|
|
|
|
module StringTrim
|
|
def normalize(x); x.gsub(/^\s*|\s*$/, '').gsub(/\s+/,' '); end
|
|
end
|
|
|
|
module StringDowncase
|
|
def normalize(x); x.downcase.gsub(/^\s*|\s*$/, '').gsub(/\s+/,' '); end
|
|
end
|
|
|
|
add('2.5.13.2', 'caseIgnoreMatch', '1.3.6.1.4.1.1466.115.1') do
|
|
extend Equality
|
|
extend StringDowncase
|
|
end
|
|
|
|
module Integer
|
|
def normalize(x); x.to_i; end
|
|
end
|
|
|
|
add('2.5.13.8', 'numericStringMatch', '1.3.6.1.4.1.1466.115.121.1.36') do
|
|
extend Equality
|
|
extend Integer
|
|
end
|
|
|
|
# TODO: Add semantics for these (difficult since RFC2252 doesn't give
|
|
# them, so we presumably have to go through X.500)
|
|
add('2.5.13.11', 'caseIgnoreListMatch', '1.3.6.1.4.1.1466.115.121.1.41')
|
|
add('2.5.13.14', 'integerMatch', '1.3.6.1.4.1.1466.115.121.1.27') do
|
|
extend Equality
|
|
extend Integer
|
|
end
|
|
add('2.5.13.16', 'bitStringMatch', '1.3.6.1.4.1.1466.115.121.1.6')
|
|
add('2.5.13.20', 'telephoneNumberMatch', '1.3.6.1.4.1.1466.115.121.1.50') do
|
|
extend Equality
|
|
extend StringTrim
|
|
end
|
|
add('2.5.13.22', 'presentationAddressMatch', '1.3.6.1.4.1.1466.115.121.1.43')
|
|
add('2.5.13.23', 'uniqueMemberMatch', '1.3.6.1.4.1.1466.115.121.1.34')
|
|
add('2.5.13.24', 'protocolInformationMatch', '1.3.6.1.4.1.1466.115.121.1.42')
|
|
add('2.5.13.27', 'generalizedTimeMatch', '1.3.6.1.4.1.1466.115.121.1.24') { extend Equality }
|
|
|
|
# IA5 stuff. FIXME: What's the correct way to 'downcase' UTF8 strings?
|
|
|
|
module IA5Trim
|
|
def normalize(x); x.gsub(/^\s*|\s*$/u, '').gsub(/\s+/u,' '); end
|
|
end
|
|
|
|
module IA5Downcase
|
|
def normalize(x); x.downcase.gsub(/^\s*|\s*$/u, '').gsub(/\s+/u,' '); end
|
|
end
|
|
|
|
add('1.3.6.1.4.1.1466.109.114.1', 'caseExactIA5Match', '1.3.6.1.4.1.1466.115.121.1.26') do
|
|
extend Equality
|
|
extend IA5Trim
|
|
end
|
|
|
|
add('1.3.6.1.4.1.1466.109.114.2', 'caseIgnoreIA5Match', '1.3.6.1.4.1.1466.115.121.1.26') do
|
|
extend Equality
|
|
extend IA5Downcase
|
|
end
|
|
|
|
add('2.5.13.28', 'generalizedTimeOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.24') { extend Ordering }
|
|
add('2.5.13.3', 'caseIgnoreOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.15') do
|
|
extend Ordering
|
|
extend StringDowncase
|
|
end
|
|
|
|
add('2.5.13.4', 'caseIgnoreSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
|
extend Substrings
|
|
extend StringDowncase
|
|
end
|
|
add('2.5.13.21', 'telephoneNumberSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
|
extend Substrings
|
|
end
|
|
add('2.5.13.10', 'numericStringSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
|
extend Substrings
|
|
end
|
|
|
|
# from OpenLDAP
|
|
add('1.3.6.1.4.1.4203.1.2.1', 'caseExactIA5SubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.26') do
|
|
extend Substrings
|
|
extend IA5Trim
|
|
end
|
|
add('1.3.6.1.4.1.1466.109.114.3', 'caseIgnoreIA5SubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.26') do
|
|
extend Substrings
|
|
extend IA5Downcase
|
|
end
|
|
add('2.5.13.5', 'caseExactMatch', '1.3.6.1.4.1.1466.115.121.1.15') { extend Equality }
|
|
add('2.5.13.6', 'caseExactOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.15') { extend Ordering }
|
|
add('2.5.13.7', 'caseExactSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') { extend Substrings }
|
|
add('2.5.13.9', 'numericStringOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.36') { extend Ordering; extend Integer }
|
|
add('2.5.13.13', 'booleanMatch', '1.3.6.1.4.1.1466.115.121.1.7') do
|
|
extend Equality
|
|
def self.normalize(x)
|
|
return true if x == 'TRUE'
|
|
return false if x == 'FALSE'
|
|
x
|
|
end
|
|
end
|
|
add('2.5.13.15', 'integerOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.27') { extend Ordering; extend Integer }
|
|
add('2.5.13.17', 'octetStringMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Equality }
|
|
add('2.5.13.18', 'octetStringOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Ordering }
|
|
add('2.5.13.19', 'octetStringSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Substrings }
|
|
|
|
end # class MatchingRule
|
|
|
|
end # class Server
|
|
end # module LDAP
|