ldaptic 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +41 -0
- data/lib/ldaptic.rb +151 -0
- data/lib/ldaptic/active_model.rb +37 -0
- data/lib/ldaptic/adapters.rb +90 -0
- data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
- data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
- data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
- data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
- data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
- data/lib/ldaptic/attribute_set.rb +283 -0
- data/lib/ldaptic/dn.rb +365 -0
- data/lib/ldaptic/entry.rb +646 -0
- data/lib/ldaptic/error_set.rb +34 -0
- data/lib/ldaptic/errors.rb +136 -0
- data/lib/ldaptic/escape.rb +110 -0
- data/lib/ldaptic/filter.rb +282 -0
- data/lib/ldaptic/methods.rb +387 -0
- data/lib/ldaptic/railtie.rb +9 -0
- data/lib/ldaptic/schema.rb +246 -0
- data/lib/ldaptic/syntaxes.rb +319 -0
- data/test/core.schema +582 -0
- data/test/ldaptic_active_model_test.rb +40 -0
- data/test/ldaptic_adapters_test.rb +35 -0
- data/test/ldaptic_attribute_set_test.rb +57 -0
- data/test/ldaptic_dn_test.rb +110 -0
- data/test/ldaptic_entry_test.rb +22 -0
- data/test/ldaptic_errors_test.rb +23 -0
- data/test/ldaptic_escape_test.rb +47 -0
- data/test/ldaptic_filter_test.rb +53 -0
- data/test/ldaptic_hierarchy_test.rb +90 -0
- data/test/ldaptic_schema_test.rb +44 -0
- data/test/ldaptic_syntaxes_test.rb +66 -0
- data/test/mock_adapter.rb +47 -0
- data/test/rbslapd1.rb +111 -0
- data/test/rbslapd4.rb +172 -0
- data/test/test_helper.rb +2 -0
- metadata +146 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Ldaptic
|
|
2
|
+
class ErrorSet < Hash
|
|
3
|
+
def initialize(base)
|
|
4
|
+
@base = base
|
|
5
|
+
super() { |h, k| h[k] = [] }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def add(attribute, message)
|
|
9
|
+
self[attribute] << message
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each
|
|
13
|
+
each_key do |attribute|
|
|
14
|
+
self[attribute].each do |message|
|
|
15
|
+
yield attribute, message
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def full_messages
|
|
21
|
+
map do |attribute, message|
|
|
22
|
+
"#{@base.class.human_attribute_name(attribute)} #{message}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_a
|
|
27
|
+
full_messages
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def size
|
|
31
|
+
full_messages.size
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module Ldaptic
|
|
2
|
+
|
|
3
|
+
class Error < ::RuntimeError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class EntryNotSaved < Error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# All server errors are instances of this class. The error message and error
|
|
10
|
+
# code can be accessed with <tt>exception.message</tt> and
|
|
11
|
+
# <tt>exception.code</tt> respectively.
|
|
12
|
+
class ServerError < Error
|
|
13
|
+
attr_accessor :code
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The module houses all subclasses of Ldaptic::ServerError. The methods
|
|
17
|
+
# contained within are for internal use only.
|
|
18
|
+
module Errors
|
|
19
|
+
|
|
20
|
+
#{
|
|
21
|
+
# 0=>"Success",
|
|
22
|
+
# 1=>"Operations error",
|
|
23
|
+
# 2=>"Protocol error",
|
|
24
|
+
# 3=>"Time limit exceeded",
|
|
25
|
+
# 4=>"Size limit exceeded",
|
|
26
|
+
# 5=>"Compare False",
|
|
27
|
+
# 6=>"Compare True",
|
|
28
|
+
# 7=>"Authentication method not supported"
|
|
29
|
+
# 8=>"Strong(er) authentication required",
|
|
30
|
+
# 9=>"Partial results and referral received",
|
|
31
|
+
# 10=>"Referral",
|
|
32
|
+
# 11=>"Administrative limit exceeded",
|
|
33
|
+
# 12=>"Critical extension is unavailable",
|
|
34
|
+
# 13=>"Confidentiality required",
|
|
35
|
+
# 14=>"SASL bind in progress",
|
|
36
|
+
# 16=>"No such attribute",
|
|
37
|
+
# 17=>"Undefined attribute type",
|
|
38
|
+
# 18=>"Inappropriate matching",
|
|
39
|
+
# 19=>"Constraint violation",
|
|
40
|
+
# 20=>"Type or value exists",
|
|
41
|
+
# 21=>"Invalid syntax",
|
|
42
|
+
# 32=>"No such object",
|
|
43
|
+
# 33=>"Alias problem",
|
|
44
|
+
# 34=>"Invalid DN syntax",
|
|
45
|
+
# 35=>"Entry is a leaf",
|
|
46
|
+
# 36=>"Alias dereferencing problem",
|
|
47
|
+
# 47=>"Proxy Authorization Failure",
|
|
48
|
+
# 48=>"Inappropriate authentication",
|
|
49
|
+
# 49=>"Invalid credentials",
|
|
50
|
+
# 50=>"Insufficient access",
|
|
51
|
+
# 51=>"Server is busy",
|
|
52
|
+
# 52=>"Server is unavailable",
|
|
53
|
+
# 53=>"Server is unwilling to perform",
|
|
54
|
+
# 54=>"Loop detected",
|
|
55
|
+
# 64=>"Naming violation",
|
|
56
|
+
# 65=>"Object class violation",
|
|
57
|
+
# 66=>"Operation not allowed on non-leaf",
|
|
58
|
+
# 67=>"Operation not allowed on RDN",
|
|
59
|
+
# 68=>"Already exists",
|
|
60
|
+
# 69=>"Cannot modify object class",
|
|
61
|
+
# 70=>"Results too large",
|
|
62
|
+
# 71=>"Operation affects multiple DSAs",
|
|
63
|
+
# 80=>"Internal (implementation specific) error",
|
|
64
|
+
# 81=>"Can't contact LDAP server",
|
|
65
|
+
# 82=>"Local error",
|
|
66
|
+
# 83=>"Encoding error",
|
|
67
|
+
# 84=>"Decoding error",
|
|
68
|
+
# 85=>"Timed out",
|
|
69
|
+
# 86=>"Unknown authentication method",
|
|
70
|
+
# 87=>"Bad search filter",
|
|
71
|
+
# 88=>"User cancelled operation",
|
|
72
|
+
# 89=>"Bad parameter to an ldap routine",
|
|
73
|
+
# 90=>"Out of memory",
|
|
74
|
+
# 91=>"Connect error",
|
|
75
|
+
# 92=>"Not Supported",
|
|
76
|
+
# 93=>"Control not found",
|
|
77
|
+
# 94=>"No results returned",
|
|
78
|
+
# 95=>"More results to return",
|
|
79
|
+
# 96=>"Client Loop",
|
|
80
|
+
# 97=>"Referral Limit Exceeded",
|
|
81
|
+
#}
|
|
82
|
+
|
|
83
|
+
# Error code 32.
|
|
84
|
+
class NoSuchObject < ServerError
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Error code 5.
|
|
88
|
+
class CompareFalse < ServerError
|
|
89
|
+
end
|
|
90
|
+
# Error code 6.
|
|
91
|
+
class CompareTrue < ServerError
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
EXCEPTIONS = {
|
|
95
|
+
32 => NoSuchObject,
|
|
96
|
+
5 => CompareFalse,
|
|
97
|
+
6 => CompareTrue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class << self
|
|
101
|
+
|
|
102
|
+
# Provides a backtrace minus all files shipped with Ldaptic.
|
|
103
|
+
def application_backtrace
|
|
104
|
+
dir = File.dirname(File.dirname(__FILE__))
|
|
105
|
+
c = caller
|
|
106
|
+
c.shift while c.first[0,dir.length] == dir
|
|
107
|
+
c
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Raise an exception (object only, no strings or classes) with the
|
|
111
|
+
# backtrace stripped of all Ldaptic files.
|
|
112
|
+
def raise(exception)
|
|
113
|
+
exception.set_backtrace(application_backtrace)
|
|
114
|
+
Kernel.raise exception
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def for(code, message = nil) #:nodoc:
|
|
118
|
+
message ||= "Unknown error #{code}"
|
|
119
|
+
klass = EXCEPTIONS[code] || ServerError
|
|
120
|
+
exception = klass.new(message)
|
|
121
|
+
exception.code = code
|
|
122
|
+
exception
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Given an error code and a message, raise an Ldaptic::ServerError unless
|
|
126
|
+
# the code is zero. The right subclass is selected automatically if it
|
|
127
|
+
# is available.
|
|
128
|
+
def raise_unless_zero(code, message = nil)
|
|
129
|
+
raise self.for(code, message) unless code.zero?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Ldaptic
|
|
2
|
+
|
|
3
|
+
# Encode an object with LDAP semantics. Generally this is just to_s, but
|
|
4
|
+
# dates and booleans get special treatment.
|
|
5
|
+
#
|
|
6
|
+
# If a symbol is passed in, underscores are replaced by dashes, aiding in
|
|
7
|
+
# bridging the gap between LDAP and Ruby conventions.
|
|
8
|
+
def self.encode(value)
|
|
9
|
+
if value.respond_to?(:utc)
|
|
10
|
+
value.dup.utc.strftime("%Y%m%d%H%M%S") + ".%06dZ" % value.usec
|
|
11
|
+
elsif [true, false].include?(value)
|
|
12
|
+
value.to_s.upcase
|
|
13
|
+
elsif value.respond_to?(:dn)
|
|
14
|
+
value.dn.dup
|
|
15
|
+
elsif value.kind_of?(Symbol)
|
|
16
|
+
value.to_s.gsub('_', '-')
|
|
17
|
+
else
|
|
18
|
+
value.to_s.dup
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Escape a string for use in an LDAP filter, or in a DN. If the second
|
|
23
|
+
# argument is +true+, asterisks are not escaped.
|
|
24
|
+
#
|
|
25
|
+
# If the first argument is not a string, it is handed off to LDAP::encode.
|
|
26
|
+
def self.escape(string, allow_asterisks = false)
|
|
27
|
+
string = Ldaptic.encode(string)
|
|
28
|
+
enc = lambda { |l| "\\%02X" % l.ord }
|
|
29
|
+
string.gsub!(/[()\\\0-\37"+,;<>]/, &enc)
|
|
30
|
+
string.gsub!(/\A[# ]| \Z/, &enc)
|
|
31
|
+
if allow_asterisks
|
|
32
|
+
string.gsub!('**', '\\\\2A')
|
|
33
|
+
else
|
|
34
|
+
string.gsub!('*', '\\\\2A')
|
|
35
|
+
end
|
|
36
|
+
string
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.unescape(string)
|
|
40
|
+
dest = ""
|
|
41
|
+
string = string.strip # Leading and trailing whitespace MUST be encoded
|
|
42
|
+
if string[0,1] == "#"
|
|
43
|
+
[string[1..-1]].pack("H*")
|
|
44
|
+
else
|
|
45
|
+
backslash = nil
|
|
46
|
+
string.each_byte do |byte|
|
|
47
|
+
case backslash
|
|
48
|
+
when true
|
|
49
|
+
char = byte.chr
|
|
50
|
+
if ('0'..'9').include?(char) || ('a'..'f').include?(char.downcase)
|
|
51
|
+
backslash = char
|
|
52
|
+
else
|
|
53
|
+
dest << byte
|
|
54
|
+
backslash = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
when String
|
|
58
|
+
dest << (backslash << byte).to_i(16)
|
|
59
|
+
backslash = nil
|
|
60
|
+
|
|
61
|
+
else
|
|
62
|
+
backslash = nil
|
|
63
|
+
if byte == 92 # ?\\
|
|
64
|
+
backslash = true
|
|
65
|
+
else
|
|
66
|
+
dest << byte
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
dest
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Split on a given character where it is not escaped. Either an integer or
|
|
75
|
+
# string represenation of the character may be used.
|
|
76
|
+
#
|
|
77
|
+
# Ldaptic.split("a*b", '*') # => ["a","b"]
|
|
78
|
+
# Ldaptic.split("a\\*b", '*') # => ["a\\*b"]
|
|
79
|
+
# Ldaptic.split("a\\\\*b", ?*) # => ["a\\\\","b"]
|
|
80
|
+
def self.split(string, character)
|
|
81
|
+
return [] if string.empty?
|
|
82
|
+
array = [""]
|
|
83
|
+
character = character.to_str.ord if character.respond_to?(:to_str)
|
|
84
|
+
backslash = false
|
|
85
|
+
|
|
86
|
+
string.each_byte do |byte|
|
|
87
|
+
if backslash
|
|
88
|
+
array.last << byte
|
|
89
|
+
backslash = false
|
|
90
|
+
elsif byte == 92 # ?\\
|
|
91
|
+
array.last << byte
|
|
92
|
+
backslash = true
|
|
93
|
+
elsif byte == character
|
|
94
|
+
array << ""
|
|
95
|
+
else
|
|
96
|
+
array.last << byte
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
array
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class String
|
|
105
|
+
unless method_defined?(:ord)
|
|
106
|
+
def ord
|
|
107
|
+
self[0].to_i
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
require 'ldaptic/escape'
|
|
2
|
+
|
|
3
|
+
module Ldaptic
|
|
4
|
+
|
|
5
|
+
# If the argument is already a valid Ldaptic::Filter object, return it
|
|
6
|
+
# untouched. Otherwise, pass it to the appropriate constructer of the
|
|
7
|
+
# appropriate subclass.
|
|
8
|
+
#
|
|
9
|
+
# Ldaptic::Filter("(cn=Wu*)").to_s #=> '(cn=Wu*)'
|
|
10
|
+
# Ldaptic::Filter({:cn=>"Wu*"}).to_s #=> '(cn=Wu\2A)'
|
|
11
|
+
# Ldaptic::Filter(["(cn=?*)","Wu*"]).to_s #=> '(cn=Wu\2A*)'
|
|
12
|
+
def self.Filter(argument)
|
|
13
|
+
case argument
|
|
14
|
+
when Filter::Abstract then argument
|
|
15
|
+
when [],nil then nil
|
|
16
|
+
when Array then Filter::Array .new(argument)
|
|
17
|
+
when Hash then Filter::Hash .new(argument)
|
|
18
|
+
when String then Filter::String .new(argument)
|
|
19
|
+
when Symbol then Filter::Attribute.new(argument)
|
|
20
|
+
when Proc, Method
|
|
21
|
+
Ldaptic::Filter(if argument.arity > 0
|
|
22
|
+
argument.call(Filter::Spawner)
|
|
23
|
+
elsif Filter::Spawner.respond_to?(:instance_exec)
|
|
24
|
+
Filter::Spawner.instance_exec(&argument)
|
|
25
|
+
else
|
|
26
|
+
Filter::Spawner.instance_eval(&argument)
|
|
27
|
+
end)
|
|
28
|
+
else raise TypeError, "Unknown LDAP Filter type", caller
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# See Ldaptic.Filter for the contructor and Ldaptic::Filter::Abstract for
|
|
33
|
+
# methods common to all filters.
|
|
34
|
+
#
|
|
35
|
+
# Useful subclasses include String, Array, and Hash.
|
|
36
|
+
module Filter
|
|
37
|
+
|
|
38
|
+
# The filter class from which all others derive.
|
|
39
|
+
class Abstract
|
|
40
|
+
|
|
41
|
+
# Combine two filters with a logical AND.
|
|
42
|
+
def &(other)
|
|
43
|
+
And.new(self, other)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Combine two filters with a logical OR.
|
|
47
|
+
def |(other)
|
|
48
|
+
Or.new(self, other)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Negate a filter.
|
|
52
|
+
#
|
|
53
|
+
# ~Ldaptic::Filter("(a=1)").to_s # => "(!(a=1))"
|
|
54
|
+
def ~
|
|
55
|
+
Not.new(self)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Generates the filter as a string.
|
|
59
|
+
def to_s
|
|
60
|
+
process || "(objectClass=*)"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
alias to_str to_s
|
|
64
|
+
|
|
65
|
+
def inspect
|
|
66
|
+
if string = process
|
|
67
|
+
"#<#{Ldaptic::Filter.inspect} #{string}>"
|
|
68
|
+
else
|
|
69
|
+
"#<#{Ldaptic::Filter.inspect} invalid>"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_net_ldap_filter #:nodoc:
|
|
74
|
+
Net::LDAP::Filter.construct(process)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_ber #:nodoc:
|
|
78
|
+
to_net_ldap_filter.to_ber
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Spawner # :nodoc:
|
|
84
|
+
def self.method_missing(method)
|
|
85
|
+
Attribute.new(method)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Attribute < Abstract
|
|
90
|
+
def initialize(name)
|
|
91
|
+
if name.kind_of?(Symbol)
|
|
92
|
+
name = name.to_s.tr('_-', '-_')
|
|
93
|
+
end
|
|
94
|
+
@name = name
|
|
95
|
+
end
|
|
96
|
+
%w(== =~ >= <=).each do |method|
|
|
97
|
+
define_method(method) do |other|
|
|
98
|
+
Pair.new(@name, other, method)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
def process
|
|
102
|
+
"(#{@name}=*)"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# This class is used for raw LDAP queries. Note that the outermost set of
|
|
107
|
+
# parentheses *must* be used.
|
|
108
|
+
#
|
|
109
|
+
# Ldaptic::Filter("a=1") # Wrong
|
|
110
|
+
# Ldaptic::Filter("(a=1)") # Correct
|
|
111
|
+
class String < Abstract
|
|
112
|
+
|
|
113
|
+
def initialize(string) #:nodoc:
|
|
114
|
+
@string = string
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns the original string
|
|
118
|
+
def process
|
|
119
|
+
@string
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Does ? parameter substitution.
|
|
125
|
+
#
|
|
126
|
+
# Ldaptic::Filter(["(cn=?*)", "Sm"]).to_s #=> "(cn=Sm*)"
|
|
127
|
+
class Array < Abstract
|
|
128
|
+
def initialize(array) #:nodoc:
|
|
129
|
+
@template = array.first
|
|
130
|
+
@parameters = array[1..-1]
|
|
131
|
+
end
|
|
132
|
+
def process
|
|
133
|
+
parameters = @parameters.dup
|
|
134
|
+
string = @template.gsub('?') { Ldaptic.escape(parameters.pop) }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Used in the implementation of Ldaptic::Filter::And and
|
|
139
|
+
# Ldaptic::Filter::Or. For internal use only.
|
|
140
|
+
class Join < Abstract
|
|
141
|
+
def initialize(operator, *args) #:nodoc:
|
|
142
|
+
@array = [operator] + args.map {|arg| Ldaptic::Filter(arg)}
|
|
143
|
+
end
|
|
144
|
+
def process
|
|
145
|
+
"(#{@array*''})" if @array.compact.size > 1
|
|
146
|
+
end
|
|
147
|
+
def to_net_ldap_filter #:nodoc
|
|
148
|
+
@array[1..-1].inject {|m, o| m.to_net_ldap_filter.send(@array.first, o.to_net_ldap_filter)}
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class And < Join
|
|
153
|
+
def initialize(*args)
|
|
154
|
+
super(:&, *args)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class Or < Join
|
|
159
|
+
def initialize(*args)
|
|
160
|
+
super(:|, *args)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class Not < Abstract
|
|
165
|
+
def initialize(object)
|
|
166
|
+
@object = Ldaptic::Filter(object)
|
|
167
|
+
end
|
|
168
|
+
def process
|
|
169
|
+
process = @object.process and "(!#{process})"
|
|
170
|
+
end
|
|
171
|
+
def to_net_ldap_filter #:nodoc:
|
|
172
|
+
~ @object.to_net_ldap_filter
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# A hash is the most general and most useful type of filter builder.
|
|
177
|
+
#
|
|
178
|
+
# Ldaptic::Filter(
|
|
179
|
+
# :givenName => "David",
|
|
180
|
+
# :sn! => "Thomas",
|
|
181
|
+
# :postalCode => (70000..80000)
|
|
182
|
+
# ).to_s # => "(&(givenName=David)(&(postalCode>=70000)(postalCode<=80000))(!(sn=Thomas)))"
|
|
183
|
+
#
|
|
184
|
+
# Including :* => true allows asterisks to pass through unaltered.
|
|
185
|
+
# Otherwise, they are escaped.
|
|
186
|
+
#
|
|
187
|
+
# Ldaptic::Filter(:givenName => "Dav*", :* => true).to_s # => "(givenName=Dav*)"
|
|
188
|
+
class Hash < Abstract
|
|
189
|
+
|
|
190
|
+
attr_accessor :escape_asterisks
|
|
191
|
+
attr_reader :hash
|
|
192
|
+
# Call Ldaptic::Filter(hash) instead of instantiating this class
|
|
193
|
+
# directly.
|
|
194
|
+
def initialize(hash)
|
|
195
|
+
@hash = hash.dup
|
|
196
|
+
@escape_asterisks = !@hash.delete(:*)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def process
|
|
200
|
+
string = @hash.map {|k, v| [k.to_s, v]}.sort.map do |(k, v)|
|
|
201
|
+
Pair.new(k, v, @escape_asterisks ? "==" : "=~").process
|
|
202
|
+
end.join
|
|
203
|
+
case @hash.size
|
|
204
|
+
when 0 then nil
|
|
205
|
+
when 1 then string
|
|
206
|
+
else "(&#{string})"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Internal class used to process a single entry from a hash.
|
|
212
|
+
class Pair < Abstract
|
|
213
|
+
INVERSE_OPERATORS = {
|
|
214
|
+
"!=" => "==",
|
|
215
|
+
"!~" => "=~",
|
|
216
|
+
">" => "<=",
|
|
217
|
+
"<" => ">="
|
|
218
|
+
}
|
|
219
|
+
def initialize(key, value, operator)
|
|
220
|
+
@key, @value, @operator = key.to_s.dup, value, operator.to_s
|
|
221
|
+
@inverse = !!@key.sub!(/!$/, '')
|
|
222
|
+
if op = INVERSE_OPERATORS[@operator]
|
|
223
|
+
@inverse ^= true
|
|
224
|
+
@operator = op
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def process
|
|
229
|
+
k = @key
|
|
230
|
+
v = @value
|
|
231
|
+
if @operator == "=~"
|
|
232
|
+
operator = "=="
|
|
233
|
+
star = true
|
|
234
|
+
else
|
|
235
|
+
operator = @operator
|
|
236
|
+
star = false
|
|
237
|
+
end
|
|
238
|
+
inverse = @inverse
|
|
239
|
+
operator = "=" if operator == "=="
|
|
240
|
+
if v.respond_to?(:to_ary)
|
|
241
|
+
q = "(|" + v.map {|e| "(#{Ldaptic.encode(k)}=#{Ldaptic.escape(e, star)})"}.join + ")"
|
|
242
|
+
elsif v.kind_of?(Range)
|
|
243
|
+
q = []
|
|
244
|
+
if v.first != -1.0/0
|
|
245
|
+
q << "(#{Ldaptic.encode(k)}>=#{Ldaptic.escape(v.first, star)})"
|
|
246
|
+
end
|
|
247
|
+
if v.last != 1.0/0
|
|
248
|
+
if v.exclude_end?
|
|
249
|
+
q << "(!(#{Ldaptic.encode(k)}>=#{Ldaptic.escape(v.last, star)}))"
|
|
250
|
+
else
|
|
251
|
+
q << "(#{Ldaptic.encode(k)}<=#{Ldaptic.escape(v.last, star)})"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
q = "(&#{q*""})"
|
|
255
|
+
elsif v == true || v == :*
|
|
256
|
+
q = "(#{Ldaptic.encode(k)}=*)"
|
|
257
|
+
elsif !v
|
|
258
|
+
q = "(#{Ldaptic.encode(k)}=*)"
|
|
259
|
+
inverse ^= true
|
|
260
|
+
else
|
|
261
|
+
q = "(#{Ldaptic.encode(k)}#{operator}#{Ldaptic.escape(v, star)})"
|
|
262
|
+
end
|
|
263
|
+
inverse ? "(!#{q})" : q
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
module Conversions #:nodoc:
|
|
268
|
+
def to_ldap_filter
|
|
269
|
+
Ldaptic::Filter(self)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class Hash
|
|
278
|
+
include Ldaptic::Filter::Conversions
|
|
279
|
+
end
|
|
280
|
+
class String
|
|
281
|
+
include Ldaptic::Filter::Conversions
|
|
282
|
+
end
|