rom-ldap 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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