rom-ldap 0.2.2

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.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +251 -0
  3. data/CONTRIBUTING.md +18 -0
  4. data/README.md +172 -0
  5. data/TODO.md +33 -0
  6. data/config/responses.yml +328 -0
  7. data/lib/dry/monitor/ldap/colorizers/default.rb +17 -0
  8. data/lib/dry/monitor/ldap/colorizers/rouge.rb +31 -0
  9. data/lib/dry/monitor/ldap/logger.rb +58 -0
  10. data/lib/rom-ldap.rb +1 -0
  11. data/lib/rom/ldap.rb +22 -0
  12. data/lib/rom/ldap/alias.rb +30 -0
  13. data/lib/rom/ldap/associations.rb +6 -0
  14. data/lib/rom/ldap/associations/core.rb +23 -0
  15. data/lib/rom/ldap/associations/many_to_many.rb +18 -0
  16. data/lib/rom/ldap/associations/many_to_one.rb +22 -0
  17. data/lib/rom/ldap/associations/one_to_many.rb +32 -0
  18. data/lib/rom/ldap/associations/one_to_one.rb +19 -0
  19. data/lib/rom/ldap/associations/self_ref.rb +35 -0
  20. data/lib/rom/ldap/attribute.rb +327 -0
  21. data/lib/rom/ldap/client.rb +185 -0
  22. data/lib/rom/ldap/client/authentication.rb +118 -0
  23. data/lib/rom/ldap/client/operations.rb +233 -0
  24. data/lib/rom/ldap/commands.rb +6 -0
  25. data/lib/rom/ldap/commands/create.rb +41 -0
  26. data/lib/rom/ldap/commands/delete.rb +17 -0
  27. data/lib/rom/ldap/commands/update.rb +35 -0
  28. data/lib/rom/ldap/constants.rb +193 -0
  29. data/lib/rom/ldap/dataset.rb +286 -0
  30. data/lib/rom/ldap/dataset/conversion.rb +62 -0
  31. data/lib/rom/ldap/dataset/dsl.rb +299 -0
  32. data/lib/rom/ldap/dataset/persistence.rb +44 -0
  33. data/lib/rom/ldap/directory.rb +126 -0
  34. data/lib/rom/ldap/directory/capabilities.rb +71 -0
  35. data/lib/rom/ldap/directory/entry.rb +200 -0
  36. data/lib/rom/ldap/directory/env.rb +155 -0
  37. data/lib/rom/ldap/directory/operations.rb +282 -0
  38. data/lib/rom/ldap/directory/password.rb +122 -0
  39. data/lib/rom/ldap/directory/root.rb +187 -0
  40. data/lib/rom/ldap/directory/tokenization.rb +66 -0
  41. data/lib/rom/ldap/directory/transactions.rb +31 -0
  42. data/lib/rom/ldap/directory/vendors/active_directory.rb +129 -0
  43. data/lib/rom/ldap/directory/vendors/apache_ds.rb +27 -0
  44. data/lib/rom/ldap/directory/vendors/e_directory.rb +16 -0
  45. data/lib/rom/ldap/directory/vendors/open_directory.rb +12 -0
  46. data/lib/rom/ldap/directory/vendors/open_dj.rb +25 -0
  47. data/lib/rom/ldap/directory/vendors/open_ldap.rb +35 -0
  48. data/lib/rom/ldap/directory/vendors/three_eight_nine.rb +16 -0
  49. data/lib/rom/ldap/directory/vendors/unknown.rb +22 -0
  50. data/lib/rom/ldap/dsl.rb +76 -0
  51. data/lib/rom/ldap/errors.rb +47 -0
  52. data/lib/rom/ldap/expression.rb +77 -0
  53. data/lib/rom/ldap/expression_encoder.rb +174 -0
  54. data/lib/rom/ldap/extensions.rb +50 -0
  55. data/lib/rom/ldap/extensions/active_support_notifications.rb +26 -0
  56. data/lib/rom/ldap/extensions/compatibility.rb +11 -0
  57. data/lib/rom/ldap/extensions/dsml.rb +165 -0
  58. data/lib/rom/ldap/extensions/msgpack.rb +23 -0
  59. data/lib/rom/ldap/extensions/optimised_json.rb +25 -0
  60. data/lib/rom/ldap/extensions/rails_log_subscriber.rb +38 -0
  61. data/lib/rom/ldap/formatter.rb +26 -0
  62. data/lib/rom/ldap/functions.rb +207 -0
  63. data/lib/rom/ldap/gateway.rb +145 -0
  64. data/lib/rom/ldap/ldif.rb +74 -0
  65. data/lib/rom/ldap/ldif/exporter.rb +77 -0
  66. data/lib/rom/ldap/ldif/importer.rb +95 -0
  67. data/lib/rom/ldap/mapper_compiler.rb +19 -0
  68. data/lib/rom/ldap/matchers.rb +69 -0
  69. data/lib/rom/ldap/message_queue.rb +7 -0
  70. data/lib/rom/ldap/oid.rb +101 -0
  71. data/lib/rom/ldap/parsers/abstract_syntax.rb +91 -0
  72. data/lib/rom/ldap/parsers/attribute.rb +290 -0
  73. data/lib/rom/ldap/parsers/filter_syntax.rb +133 -0
  74. data/lib/rom/ldap/pdu.rb +285 -0
  75. data/lib/rom/ldap/plugin/pagination.rb +145 -0
  76. data/lib/rom/ldap/plugins.rb +7 -0
  77. data/lib/rom/ldap/projection_dsl.rb +38 -0
  78. data/lib/rom/ldap/relation.rb +135 -0
  79. data/lib/rom/ldap/relation/exporting.rb +72 -0
  80. data/lib/rom/ldap/relation/reading.rb +461 -0
  81. data/lib/rom/ldap/relation/writing.rb +64 -0
  82. data/lib/rom/ldap/responses.rb +17 -0
  83. data/lib/rom/ldap/restriction_dsl.rb +45 -0
  84. data/lib/rom/ldap/schema.rb +123 -0
  85. data/lib/rom/ldap/schema/attributes_inferrer.rb +59 -0
  86. data/lib/rom/ldap/schema/dsl.rb +13 -0
  87. data/lib/rom/ldap/schema/inferrer.rb +50 -0
  88. data/lib/rom/ldap/schema/type_builder.rb +133 -0
  89. data/lib/rom/ldap/scope.rb +19 -0
  90. data/lib/rom/ldap/search_request.rb +249 -0
  91. data/lib/rom/ldap/socket.rb +210 -0
  92. data/lib/rom/ldap/tasks/ldap.rake +103 -0
  93. data/lib/rom/ldap/tasks/ldif.rake +80 -0
  94. data/lib/rom/ldap/transaction.rb +29 -0
  95. data/lib/rom/ldap/type_map.rb +88 -0
  96. data/lib/rom/ldap/types.rb +158 -0
  97. data/lib/rom/ldap/version.rb +17 -0
  98. data/lib/rom/plugins/relation/ldap/active_directory.rb +182 -0
  99. data/lib/rom/plugins/relation/ldap/auto_restrictions.rb +69 -0
  100. data/lib/rom/plugins/relation/ldap/e_directory.rb +27 -0
  101. data/lib/rom/plugins/relation/ldap/instrumentation.rb +35 -0
  102. data/lib/rouge/lexers/ldap.rb +72 -0
  103. data/lib/rouge/themes/ldap.rb +49 -0
  104. metadata +231 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # Apache Directory Extension
7
+ #
8
+ # @api private
9
+ module ApacheDs
10
+ # IGNORE_ATTRS_REGEX = /^[m-|ads|entry].*$/.freeze
11
+
12
+ # @return [Array]
13
+ #
14
+ # @api public
15
+ def schema_attributes
16
+ query(base: 'ou=schema', filter: '(objectClass=metaAttributeType)')
17
+ end
18
+
19
+ # @return [Array]
20
+ #
21
+ # @api public
22
+ def schemas_classes
23
+ query(base: 'ou=schema', filter: '(objectClass=metaObjectClass)')
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # Novell eDirectory Extension
7
+ #
8
+ # @api private
9
+ module EDirectory
10
+ # WIP
11
+ def transactions
12
+ # noop
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # Apple Open Directory Extension
7
+ #
8
+ # @api private
9
+ module OpenDirectory
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # OpenDJ Server Extension
7
+ #
8
+ # @api private
9
+ module OpenDj
10
+ # @return [String]
11
+ #
12
+ # @api public
13
+ def full_vendor_version
14
+ root.first('fullVendorVersion')
15
+ end
16
+
17
+ # @return [String]
18
+ #
19
+ # @api public
20
+ def etag
21
+ root.first('etag')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # OpenLDAP Extension
7
+ #
8
+ # @api private
9
+ module OpenLdap
10
+ #
11
+ # @return [String]
12
+ #
13
+ # @api public
14
+ def vendor_name
15
+ 'OpenLDAP'
16
+ end
17
+
18
+ #
19
+ # @return [String]
20
+ #
21
+ # @api public
22
+ def vendor_version
23
+ '0.0'
24
+ end
25
+
26
+ #
27
+ # @return [String]
28
+ #
29
+ # @api public
30
+ def organization
31
+ query(base: contexts[0]).first.first('o')
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ #
6
+ # RedHat 389DS Extension
7
+ #
8
+ # @api private
9
+ module ThreeEightNine
10
+ # @api public
11
+ def netscapemdsuffix
12
+ root.first('netscapemdsuffix')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # @api private
6
+ module Unknown
7
+ # @return [String]
8
+ #
9
+ # @api public
10
+ def vendor_name
11
+ 'Unknown'
12
+ end
13
+
14
+ # @return [String]
15
+ #
16
+ # @api public
17
+ def vendor_version
18
+ 'Unknown'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+ require 'rom/support/inflector'
5
+ require 'rom/constants'
6
+ require 'rom/ldap/types'
7
+
8
+ module ROM
9
+ module LDAP
10
+ # @api private
11
+ class DSL < BasicObject
12
+
13
+ # @!attribute [r] schema
14
+ # @return [LDAP::Schema]
15
+ attr_reader :schema
16
+
17
+ # @!attribute [r] relations
18
+ # @return [Hash, RelationRegistry]
19
+ attr_reader :relations
20
+
21
+ # @!attribute [r] picked_relations
22
+ # @return [Concurrent::Map]
23
+ attr_reader :picked_relations
24
+
25
+ # @api private
26
+ def initialize(schema)
27
+ @schema = schema
28
+ @relations = schema.respond_to?(:relations) ? schema.relations : EMPTY_HASH
29
+ @picked_relations = ::Concurrent::Map.new
30
+ end
31
+
32
+ # @api private
33
+ def call(&block)
34
+ result = instance_exec(select_relations(block.parameters), &block)
35
+
36
+ if result.is_a?(::Array)
37
+ result
38
+ else
39
+ [result]
40
+ end
41
+ end
42
+
43
+ # @api private
44
+ def respond_to_missing?(name, include_private = false)
45
+ super || schema.key?(name)
46
+ end
47
+
48
+ private
49
+
50
+ # @api private
51
+ def type(identifier)
52
+ type_name = Inflector.classify(identifier)
53
+ types.const_get(type_name) if types.const_defined?(type_name)
54
+ end
55
+
56
+ # @api private
57
+ def types
58
+ ::ROM::LDAP::Types
59
+ end
60
+
61
+ # @api private
62
+ def select_relations(parameters)
63
+ @picked_relations.fetch_or_store(parameters.hash) do
64
+ keys = parameters.select { |type, _| type == :keyreq }
65
+
66
+ if keys.empty?
67
+ relations
68
+ else
69
+ keys.each_with_object({}) { |(_, k), rs| rs[k] = relations[k] }
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # @see ROM::LDAP::Gateway
6
+ #
7
+ # @see ROM::LDAP::Schema::Inferrer
8
+ #
9
+ CONNECTION_FAILURES = [
10
+ EOFError,
11
+ Errno::ECONNABORTED,
12
+ Errno::ECONNREFUSED,
13
+ Errno::ECONNRESET,
14
+ Errno::EHOSTUNREACH,
15
+ Errno::EIO,
16
+ Errno::ENETDOWN,
17
+ Errno::ENETRESET,
18
+ Errno::EPIPE,
19
+ Errno::ETIMEDOUT,
20
+ IOError
21
+ ].freeze
22
+
23
+ # @see Client::Authentication#bind
24
+ #
25
+ BindError = Class.new(StandardError)
26
+
27
+ # @see Client::Authentication#sasl_bind
28
+ #
29
+ SecureBindError = Class.new(StandardError)
30
+
31
+ # @see Client#submit
32
+ #
33
+ ResponseError = Class.new(StandardError)
34
+
35
+ # @see Directory::Operations#find, #by_dn, #add
36
+ #
37
+ DistinguishedNameError = Class.new(StandardError)
38
+
39
+ # @see Socket#connect
40
+ #
41
+ ConnectionError = Class.new(StandardError)
42
+
43
+ # @see Directory::Password#generate
44
+ #
45
+ PasswordError = Class.new(StandardError)
46
+ end
47
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # @api private
6
+ class Expression
7
+
8
+ extend Initializer
9
+
10
+ option :op, type: Types::Abstract
11
+
12
+ option :field, optional: true, type: Types::Field
13
+
14
+ option :value, optional: true, type: Types::Value
15
+
16
+ option :exps, optional: true, type: Types::Array.of(Types.Instance(Expression))
17
+
18
+ #
19
+ #
20
+ # @return [String]
21
+ #
22
+ # @api public
23
+ def to_ber
24
+ require 'rom/ldap/expression_encoder'
25
+ ExpressionEncoder.new(options).call
26
+ end
27
+
28
+ # Unbracketed filter string
29
+ #
30
+ # @return [String]
31
+ #
32
+ def to_raw_filter
33
+ case op
34
+ when :op_eql, :op_bineq then "#{field}=#{value}"
35
+ when :op_ext then "#{field}:=#{value}"
36
+ when :op_gte then "#{field}>=#{value}"
37
+ when :op_lte then "#{field}<=#{value}"
38
+ when :op_prx then "#{field}~=#{value}"
39
+ when :con_and then "&#{exps.map(&:to_filter).join}"
40
+ when :con_or then "|#{exps.map(&:to_filter).join}"
41
+ when :con_not then "!#{exps[0].to_filter}"
42
+ end
43
+ end
44
+
45
+ # Bracketed filter string
46
+ #
47
+ # @return [String]
48
+ #
49
+ def to_filter
50
+ "(#{to_raw_filter})"
51
+ end
52
+ alias_method :to_s, :to_filter
53
+
54
+ # AST with original atrributes and values
55
+ #
56
+ # @return [Array]
57
+ #
58
+ def to_ast
59
+ case op
60
+ when :con_and then [op, exps.map(&:to_ast)]
61
+ when :con_or then [op, exps.map(&:to_ast)]
62
+ when :con_not then [op, exps[0].to_ast]
63
+ else
64
+ [op, field, value]
65
+ end
66
+ end
67
+ alias_method :to_a, :to_ast
68
+
69
+ # @return [String]
70
+ #
71
+ def inspect
72
+ %(#<#{self.class} #{to_raw_filter} />)
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # Used in ROM::LDAP::Expression#to_ber.
6
+ #
7
+ # Filter ::=
8
+ # CHOICE {
9
+ # and [0] SET OF Filter,
10
+ # or [1] SET OF Filter,
11
+ # not [2] Filter,
12
+ # equalityMatch [3] AttributeValueAssertion,
13
+ # substrings [4] SubstringFilter,
14
+ # greaterOrEqual [5] AttributeValueAssertion,
15
+ # lessOrEqual [6] AttributeValueAssertion,
16
+ # present [7] AttributeType,
17
+ # approxMatch [8] AttributeValueAssertion,
18
+ # extensibleMatch [9] MatchingRuleAssertion
19
+ # }
20
+ #
21
+ # SubstringFilter ::=
22
+ # SEQUENCE {
23
+ # type AttributeType,
24
+ # SEQUENCE OF CHOICE {
25
+ # initial [0] LDAPString,
26
+ # any [1] LDAPString,
27
+ # final [2] LDAPString
28
+ # }
29
+ # }
30
+ #
31
+ # MatchingRuleAssertion ::=
32
+ # SEQUENCE {
33
+ # matchingRule [1] MatchingRuleId OPTIONAL,
34
+ # type [2] AttributeDescription OPTIONAL,
35
+ # matchValue [3] AssertionValue,
36
+ # dnAttributes [4] BOOLEAN DEFAULT FALSE
37
+ # }
38
+ #
39
+ # Matching Rule Suffixes
40
+ # Less than [.1] or .[lt]
41
+ # Less than or equal to [.2] or [.lte]
42
+ # Equality [.3] or [.eq] (default)
43
+ # Greater than or equal to [.4] or [.gte]
44
+ # Greater than [.5] or [.gt]
45
+ # Substring [.6] or [.sub]
46
+ #
47
+ #
48
+ # @api private
49
+ class ExpressionEncoder
50
+
51
+ using ::BER
52
+
53
+ extend Initializer
54
+
55
+ option :op, type: Types::Abstract
56
+
57
+ option :field, optional: true, type: Types::Field
58
+ option :value, optional: true, type: Types::Value
59
+ option :exps, optional: true, type: Types::Array.of(Types.Instance(LDAP::Expression))
60
+
61
+ # @return [BER]
62
+ #
63
+ def call
64
+ case op
65
+ when :op_eql
66
+ if value == WILDCARD
67
+ field.to_s.to_ber_contextspecific(7)
68
+ elsif /[*]/.match?(value.to_s) # substring
69
+ substring
70
+ else
71
+ to_context(3)
72
+ end
73
+ when :op_bineq
74
+ [
75
+ field.to_s.to_ber,
76
+ unescape(value).to_ber_bin
77
+ ].to_ber_contextspecific(3)
78
+ when :op_ext
79
+ extensible
80
+ when :op_gte
81
+ to_context(5)
82
+ when :op_lte
83
+ to_context(6)
84
+ when :op_prx
85
+ to_context(8)
86
+ when :con_and
87
+ exps.map(&:to_ber).to_ber_contextspecific(0)
88
+ when :con_or
89
+ exps.map(&:to_ber).to_ber_contextspecific(1)
90
+ when :con_not
91
+ exps.map(&:to_ber).to_ber_contextspecific(2)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def substring
98
+ ary = value.split(/[*]+/, -1)
99
+
100
+ if ary.first.empty?
101
+ first = nil
102
+ ary.shift
103
+ else
104
+ first = unescape(ary.shift).to_ber_contextspecific(0)
105
+ end
106
+
107
+ if ary.last.empty?
108
+ last = nil
109
+ ary.pop
110
+ else
111
+ last = unescape(ary.pop).to_ber_contextspecific(2)
112
+ end
113
+
114
+ seq = ary.map { |e| unescape(e).to_ber_contextspecific(1) }
115
+ seq.unshift(first) if first
116
+ seq.push(last) if last
117
+
118
+ [
119
+ field.to_s.to_ber,
120
+ seq.to_ber
121
+ ].to_ber_contextspecific(4)
122
+ end
123
+
124
+ def extensible
125
+ raise(Error, "Bad attribute #{field}") unless field =~ EXTENSIBLE_REGEX
126
+
127
+ type = Regexp.last_match(1)
128
+ dn = Regexp.last_match(2)
129
+ rule = Regexp.last_match(4)
130
+
131
+ seq = []
132
+ seq << rule.to_ber_contextspecific(1) unless rule.to_s.empty? # matchingRule
133
+ seq << type.to_ber_contextspecific(2) unless type.to_s.empty? # type
134
+ seq << unescape(value).to_ber_contextspecific(3) # matchingValue
135
+ seq << '1'.to_ber_contextspecific(4) unless dn.to_s.empty? # dnAttributes
136
+
137
+ seq.to_ber_contextspecific(9)
138
+ end
139
+
140
+ # Common BER encoding
141
+ #
142
+ # @return [String]
143
+ #
144
+ def to_context(int)
145
+ [
146
+ field.to_s.to_ber,
147
+ unescape(value).to_ber
148
+ ].to_ber_contextspecific(int)
149
+ end
150
+
151
+ # @note
152
+ # Don't attempt to unescape 16 byte binary data assumed to be objectGUIDs.
153
+ # The binary form of 5936AE79-664F-44EA-BCCB-5C39399514C6
154
+ # triggers a BINARY -> UTF-8 conversion error.
155
+ #
156
+ # Converts escaped characters to unescaped characters
157
+ #
158
+ # @example
159
+ # => "\\28"
160
+ #
161
+ # @return [String]
162
+ #
163
+ # @api private
164
+ def unescape(str)
165
+ if str.to_s.length.eql?(16) && str.to_s.encoding.eql?(Encoding::BINARY)
166
+ str
167
+ else
168
+ str.to_s.gsub(UNESCAPE_REGEX) { [Regexp.last_match(1).hex].pack('U') }
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+ end