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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/extensions'
4
+
5
+ module ROM
6
+ module LDAP
7
+ extend Dry::Core::Extensions
8
+
9
+ # Make entry attributes suitable method names.
10
+ #
11
+ register_extension(:compatibility) do
12
+ require 'rom/ldap/extensions/compatibility'
13
+ end
14
+
15
+ #=======================================
16
+ # Exporters
17
+ #=======================================
18
+
19
+ # Add #to_dsml method to relation instance.
20
+ #
21
+ register_extension(:dsml_export) do
22
+ require 'rom/ldap/extensions/dsml'
23
+ end
24
+
25
+ # Add #to_msgpack method to relation instance.
26
+ #
27
+ register_extension(:msgpack_export) do
28
+ require 'rom/ldap/extensions/msgpack'
29
+ end
30
+
31
+ # Patch #to_json method in relation instance.
32
+ #
33
+ register_extension(:oj_export) do
34
+ require 'rom/ldap/extensions/optimised_json'
35
+ end
36
+
37
+
38
+ #=======================================
39
+ # Autoloaded for Rails
40
+ #=======================================
41
+
42
+ register_extension(:active_support_notifications) do
43
+ require 'rom/ldap/extensions/active_support_notifications'
44
+ end
45
+
46
+ register_extension(:rails_log_subscriber) do
47
+ require 'rom/ldap/extensions/rails_log_subscriber'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/notifications'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module ActiveSupportInstrumentation
8
+ def call(filter)
9
+ ActiveSupport::Notifications.instrument(
10
+ 'ldap.rom',
11
+ ldap: 'foobar',
12
+ name: instrumentation_name,
13
+ binds: filter
14
+ ) { super }
15
+ end
16
+
17
+ private
18
+
19
+ def instrumentation_name
20
+ "ROM[#{directory.type}]"
21
+ end
22
+ end
23
+
24
+ Dataset.prepend ActiveSupportInstrumentation
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/functions'
4
+
5
+ module ROM
6
+ module LDAP
7
+ # Assign the formatting proc used to rename the Directory::Entry attributes.
8
+ #
9
+ use_formatter Functions[:to_method_name]
10
+ end
11
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/initializer'
4
+ require 'libxml'
5
+
6
+ module ROM
7
+ module LDAP
8
+ #
9
+ # Directory Service Markup Language (DSML)
10
+ #
11
+ # Refines Array and Hash with #to_dsml method.
12
+ #
13
+ # @see https://publib.boulder.ibm.com/tividd/td/ITIM/SC32-1149-01/en_US/HTML/Policy_Org_Admin395.htm
14
+ # @see Directory::Entry
15
+ # @see Relation::Exporting
16
+ #
17
+ module DSML
18
+ # Export Entry objects as DSML files.
19
+ #
20
+ # @param tuple [Entry]
21
+ #
22
+ # @api private
23
+ class Exporter
24
+
25
+ extend Initializer
26
+
27
+ include LibXML
28
+
29
+ # Dataset
30
+ #
31
+ param :tuples, type: Types::Strict::Array.of(Types::Strict::Hash)
32
+
33
+ # <?xml version="1.0" encoding="UTF-8"?>
34
+ # <dsml>
35
+ # <directory-entries>
36
+ # <entry dn="dn">
37
+ #
38
+ # @return [String]
39
+ #
40
+ # @api private
41
+ def to_dsml
42
+ doc = XML::Document.new
43
+ doc.encoding = XML::Encoding::UTF_8
44
+ doc.root = root = create_node('dsml')
45
+ root << (entries = create_node('directory-entries'))
46
+ map_tuples { |entry| entries << entry }
47
+ doc.to_s
48
+ end
49
+
50
+ private
51
+
52
+ # <entry dn="dn">
53
+ #
54
+ # @yield [LibXML::XML::Node]
55
+ #
56
+ def map_tuples
57
+ tuples.each do |tuple|
58
+ next if tuple.empty?
59
+
60
+ dn = tuple.delete('dn')
61
+ objc = tuple.delete('objectClass')
62
+
63
+ entry_node = create_node('entry', dn: dn&.first)
64
+
65
+ classes(objc) { |c| entry_node << c }
66
+
67
+ attributes(tuple) { |a| entry_node << a }
68
+
69
+ yield(entry_node)
70
+ end
71
+ end
72
+
73
+ # Returns "<objectclass/>" if param is nil
74
+ #
75
+ # <oc-value>inetOrgPerson</oc-value>
76
+ #
77
+ # @param [Array, Nil] values
78
+ #
79
+ # @yield [LibXML::XML::Node]
80
+ #
81
+ def classes(values)
82
+ class_node = create_node('objectclass')
83
+ values.to_a.each do |value|
84
+ value_node = create_node('oc-value')
85
+ value_node.content = value
86
+ class_node << value_node
87
+ end
88
+ yield(class_node)
89
+ end
90
+
91
+ # @example
92
+ #
93
+ # {'cn'=>['Peter']}
94
+ # => <attr name="cn"><value>Peter</value></attr>
95
+ #
96
+ # @yield [LibXML::XML::Node]
97
+ #
98
+ def attributes(attrs)
99
+ attrs.each do |attr_name, attr_values|
100
+ attr_node = create_node('attr', name: attr_name)
101
+ attr_values.each do |value|
102
+ value_node = create_node('value')
103
+ value_node.content = value
104
+ attr_node << value_node
105
+ end
106
+ yield(attr_node)
107
+ end
108
+ end
109
+
110
+ #
111
+ # @return [LibXML::XML::Node]
112
+ #
113
+ def create_node(type, params = EMPTY_OPTS)
114
+ node = XML::Node.new(type)
115
+ unless params.empty?
116
+ params.each do |key, value|
117
+ XML::Attr.new(node, key.to_s, value.to_s)
118
+ end
119
+ end
120
+ node
121
+ end
122
+
123
+ end
124
+
125
+ # Extend functionality of Hash class.
126
+ #
127
+ refine ::Hash do
128
+ # Convert hash to DSML format
129
+ #
130
+ # @return [String]
131
+ #
132
+ # @api public
133
+ def to_dsml
134
+ Exporter.new([self]).to_dsml
135
+ end
136
+ end
137
+
138
+ # Extend functionality of Array class.
139
+ #
140
+ refine ::Array do
141
+ # Convert array to DSML format
142
+ #
143
+ # @return [String]
144
+ #
145
+ # @api public
146
+ def to_dsml
147
+ Exporter.new(self).to_dsml
148
+ end
149
+ end
150
+ end
151
+
152
+ module DSMLExport
153
+ using DSML
154
+ #
155
+ # @return [String]
156
+ #
157
+ # @api public
158
+ def to_dsml
159
+ export.to_dsml
160
+ end
161
+ end
162
+
163
+ Relation.include DSMLExport
164
+ end
165
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module MsgPackExport
8
+ # Export the relation as MessagePack Binary
9
+ #
10
+ # @return [String]
11
+ #
12
+ # @example
13
+ # relation.to_msgpack
14
+ #
15
+ # @api public
16
+ def to_msgpack
17
+ export.to_msgpack
18
+ end
19
+ end
20
+
21
+ Relation.include MsgPackExport
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module OptimisedJSON
8
+ # Replace #to_json
9
+ #
10
+ # @param _opts [Mixed] compatibility with JSON.generate
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @example
15
+ # relation.to_json
16
+ #
17
+ # @api public
18
+ def to_json(_opts = nil)
19
+ Oj.generate(export)
20
+ end
21
+ end
22
+
23
+ Relation.include OptimisedJSON
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module ROM
6
+ module LDAP
7
+ class RailsLogSubscriber < ActiveSupport::LogSubscriber
8
+
9
+ def ldap(event)
10
+ return unless logger.debug?
11
+
12
+ payload = event.payload
13
+
14
+ name = format('%s (%.1fms)', payload[:name], event.duration)
15
+ ldap = payload[:ldap].squeeze(' ')
16
+ binds = payload[:binds].to_a.inspect if payload[:binds]
17
+
18
+ if odd?
19
+ name = color(name, :cyan, true)
20
+ ldap = color(ldap, nil, true)
21
+ else
22
+ name = color(name, :magenta, true)
23
+ end
24
+
25
+ debug " #{name} #{ldap} #{binds}"
26
+ end
27
+
28
+ attr_reader :odd_or_even
29
+ private :odd_or_even
30
+ def odd?
31
+ @odd_or_even = !odd_or_even
32
+ end
33
+
34
+ end
35
+
36
+ RailsLogSubscriber.attach_to(:rom)
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # Proc that returns input value.
6
+ #
7
+ DEFAULT_FORMATTER = ->(v) { v }
8
+
9
+ # Set/Reset the formatting proc
10
+ #
11
+ # @param func [Proc] Callable object
12
+ #
13
+ def self.use_formatter(func = nil)
14
+ @formatter = func
15
+ end
16
+
17
+ # @see 'rom/ldap/extensions/compatibility'
18
+ #
19
+ # @example
20
+ # ROM::LDAP.load_extensions :compatibility
21
+ #
22
+ def self.formatter
23
+ @formatter || DEFAULT_FORMATTER
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/transformer/all'
4
+ require 'base64'
5
+ require 'rom/support/inflector'
6
+
7
+ module ROM
8
+ module LDAP
9
+ # @api private
10
+ module Functions
11
+ extend Dry::Transformer::Registry
12
+
13
+ import Dry::Transformer::Coercions
14
+ import Dry::Transformer::ArrayTransformations
15
+ import Dry::Transformer::HashTransformations
16
+
17
+ # Build tuple from arguments.
18
+ # Translates keys into original schema names and stringify values.
19
+ #
20
+ # @param tuple [Hash] input arguments for directory #add and #modify
21
+ #
22
+ # @return [Hash]
23
+ #
24
+ # @note Directory#add will receive a hash with key :dn
25
+ #
26
+ # @api private
27
+ def self.tuplify(tuple, matrix)
28
+ fn = t(:rename_keys, matrix) >>
29
+ t(:map_values, t(:identify_value)) >>
30
+ t(:map_values, t(:stringify)) >> t(:reject_blank)
31
+
32
+ fn.call(tuple)
33
+ end
34
+
35
+ # remove keys with blank values
36
+ # nil values allow attribute to be deleted
37
+ #
38
+ def self.reject_blank(tuple)
39
+ tuple.reject { |_k, v| v&.empty? }
40
+ end
41
+
42
+ # Map from
43
+ #
44
+ # @todo Finish documentation
45
+ #
46
+ # @param val [Symbol,String]
47
+ #
48
+ # @example
49
+ # id_value(true) => 'TRUE'
50
+ # id_value('TRUE') => true
51
+ # id_value('peter hamilton') => 'peter hamilton'
52
+ #
53
+ # @return [Mixed]
54
+ #
55
+ # @api private
56
+ def self.identify_value(val)
57
+ case val
58
+ when ::Symbol, ::TrueClass, ::FalseClass, ::NilClass
59
+ VALUES_MAP.fetch(val, val)
60
+ else
61
+ VALUES_MAP.invert.fetch(val, val)
62
+ end
63
+ end
64
+
65
+ # Ensure tuple values are strings or nil
66
+ #
67
+ # @param value [Mixed]
68
+ #
69
+ # @return [String, NilClass]
70
+ #
71
+ # @api private
72
+ def self.stringify(value)
73
+ case value
74
+ when ::Numeric then value.to_s
75
+ when ::Enumerable then value.map(&:to_s)
76
+ when ::Hash then value.to_json
77
+ when ::String then value
78
+ else
79
+ value
80
+ end
81
+ end
82
+
83
+ # Compare Magic Bytes
84
+ #
85
+ # @see https://en.wikipedia.org/wiki/List_of_file_signatures
86
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
87
+ # @see https://github.com/sdsykes/fastimage/blob/master/lib/fastimage.rb
88
+ #
89
+ # @param value [String] UTF-8 encoded
90
+ #
91
+ def self.mime_type(value)
92
+ mime =
93
+ case value[0, 2]
94
+ when "\xFF\xD8".b
95
+ 'image/jpeg'
96
+ when "\x89P".b
97
+ 'image/png'
98
+ when 'BM'
99
+ 'image/bitmap'
100
+ when 'II', 'MM'
101
+ 'image/tiff'
102
+ when "\xFF\xFBID".b
103
+ 'audio/mpeg'
104
+ when 'WA'
105
+ 'audio/x-wav'
106
+ else
107
+ 'application/octet-stream'
108
+ end
109
+ to_base64(value).prepend("data:#{mime};base64,")
110
+ end
111
+
112
+ # Base64 encoded string, with optional new line characters.
113
+ #
114
+ # @return [String]
115
+ #
116
+ def self.to_base64(value, strict: true)
117
+ if strict
118
+ Base64.strict_encode64(value).chomp
119
+ else
120
+ # [value].pack('m').chomp
121
+ Base64.encode64(value).chomp
122
+ end
123
+ end
124
+
125
+ #
126
+ #
127
+ # @return [Boolean]
128
+ #
129
+ def self.to_boolean(value)
130
+ Dry::Transformer::Coercions::BOOLEAN_MAP.fetch(value.to_s.downcase)
131
+ end
132
+
133
+ # The 18-digit Active Directory timestamps, also named
134
+ # 'Windows NT time format','Win32 FILETIME or SYSTEMTIME' or 'NTFS file time'.
135
+ #
136
+ # These are used in Microsoft Active Directory for
137
+ # pwdLastSet, accountExpires, LastLogon, LastLogonTimestamp and LastPwdSet.
138
+ #
139
+ # The number of 100-nanoseconds intervals since 12:00 A.M. January 1st, 1601 (UTC).
140
+ # NB:
141
+ # 1 nanosecond = a billionth of a second
142
+ # Accurate to the nearest millisecond (7 digits)
143
+ #
144
+ # @see ROM::LDAP::Types::Time
145
+ # @see https://ldapwiki.com/wiki/Microsoft%20TIME
146
+ #
147
+ # @param value [String] time or integer
148
+ #
149
+ # @return [Time] UTC formatted
150
+ #
151
+ def self.to_time(value)
152
+ unix_epoch_time = (Integer(value) / TEN_MILLION) - SINCE_1601
153
+ ::Time.at(unix_epoch_time).utc
154
+ rescue ArgumentError
155
+ ::Time.parse(value).utc
156
+ end
157
+
158
+ # @return [Array<String>]
159
+ #
160
+ def self.map_to_base64(values)
161
+ t(:map_array, t(:to_base64)).call(values)
162
+ end
163
+
164
+ # @return [Array<Integer>]
165
+ #
166
+ def self.map_to_integers(tuples)
167
+ t(:map_array, t(:to_integer)).call(tuples)
168
+ end
169
+
170
+ # @return [Array<Symbol>]
171
+ #
172
+ def self.map_to_symbols(tuples)
173
+ t(:map_array, t(:to_symbol)).call(tuples)
174
+ end
175
+
176
+ # @return [Array<TrueClass, FalseClass>]
177
+ #
178
+ def self.map_to_booleans(values)
179
+ t(:map_array, t(:to_boolean)).call(values)
180
+ end
181
+
182
+ # @return [Array<Time>]
183
+ #
184
+ def self.map_to_times(values)
185
+ t(:map_array, t(:to_time)).call(values)
186
+ end
187
+
188
+ # Convert string to snake case.
189
+ #
190
+ # @param value [String]
191
+ #
192
+ # @return [String]
193
+ #
194
+ def self.to_underscore(value)
195
+ Inflector.underscore(value.delete('= '))
196
+ end
197
+
198
+ # Function applied to Directory::Entry to format incoming attribute names.
199
+ #
200
+ # @api public
201
+ def self.to_method_name(value)
202
+ fn = t(:to_string) >> t(:to_underscore) >> t(:to_symbol)
203
+ fn.call(value)
204
+ end
205
+ end
206
+ end
207
+ end