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,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ class Relation < ROM::Relation
6
+
7
+ module Writing
8
+ # @example
9
+ # relation.insert(
10
+ # dn: 'uid=batman,ou=comic,dc=rom,dc=ldap',
11
+ # cn: 'The Dark Knight',
12
+ # uid: 'batman',
13
+ # given_name: 'Bruce',
14
+ # sn: 'Wayne',
15
+ # apple_imhandle: 'bruce-wayne',
16
+ # object_class: %w[extensibleObject inetOrgPerson]
17
+ # )
18
+ # #=>
19
+ # {
20
+ # dn: 'uid=batman,ou=comic,dc=rom,dc=ldap',
21
+ # cn: 'The Dark Knight',
22
+ # uid: 'batman',
23
+ # given_name: 'Bruce',
24
+ # sn: 'Wayne',
25
+ # apple_imhandle: 'bruce-wayne',
26
+ # object_class: %w[top extensibleObject inetOrgPerson]
27
+ # }
28
+ #
29
+ # @param tuple [Hash]
30
+ #
31
+ # @return [Array<Directory::Entry, FalseClass>]
32
+ #
33
+ # @api public
34
+ def insert(tuple)
35
+ dataset.add(tuple)
36
+ end
37
+
38
+ # @example
39
+ # relation.update(mail: 'fear_the_bat@gotham.com')
40
+ # #=> {}
41
+ #
42
+ # @param tuple [Hash]
43
+ #
44
+ # @return [Array<Directory::Entry, FalseClass>]
45
+ #
46
+ # @api public
47
+ def update(tuple)
48
+ dataset.modify(tuple)
49
+ end
50
+
51
+ # @example
52
+ # relation.delete #=> { uid: 'batman'}
53
+ #
54
+ # @return [Array<Directory::Entry, FalseClass>]
55
+ #
56
+ # @api public
57
+ def delete
58
+ dataset.delete
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+
6
+ module ROM
7
+ module LDAP
8
+ #
9
+ # Loaded by PDU
10
+ #
11
+ # @see https://tools.ietf.org/html/rfc4511#section-4.1.9
12
+ #
13
+ RESPONSES_PATH = Pathname(__dir__).join('../../../config/responses.yml').realpath.freeze
14
+
15
+ RESPONSES = ::YAML.load_file(RESPONSES_PATH).freeze
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/dsl'
4
+ require 'rom/ldap/parsers/filter_syntax'
5
+
6
+ module ROM
7
+ module LDAP
8
+ # @api private
9
+ class RestrictionDSL < DSL
10
+
11
+ # @api private
12
+ def call(&block)
13
+ instance_exec(select_relations(block.parameters), &block)
14
+ end
15
+
16
+ # Parse a raw query to AST.
17
+ #
18
+ # @param [String] value
19
+ #
20
+ # @return [AST]
21
+ #
22
+ # @example
23
+ # animals.where { `(cn=dodo)` }.count
24
+ #
25
+ # @api public
26
+ def `(value)
27
+ Parsers::FilterSyntax.new(value, EMPTY_ARRAY).call
28
+ end
29
+
30
+ private
31
+
32
+ # @return [Attribute, Type]
33
+ #
34
+ # @api private
35
+ def method_missing(meth, *args, &block)
36
+ if schema.key?(meth)
37
+ schema[meth]
38
+ else
39
+ type(meth)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/schema'
4
+ require 'rom/ldap/schema/dsl'
5
+ require 'rom/ldap/restriction_dsl'
6
+ require 'rom/ldap/projection_dsl'
7
+ require 'rom/ldap/schema/inferrer'
8
+
9
+ module ROM
10
+ module LDAP
11
+ class Schema < ROM::Schema
12
+
13
+ # Open restriction DSL for defining query conditions using schema attributes
14
+ #
15
+ # @see Relation#where
16
+ #
17
+ # @return [Mixed] Result of the block call
18
+ #
19
+ # @api public
20
+ def restriction(&block)
21
+ RestrictionDSL.new(self).call(&block)
22
+ end
23
+
24
+ # Create a new relation based on the schema definition
25
+ #
26
+ # @param relation [Relation] The source relation
27
+ #
28
+ # @return [Relation]
29
+ #
30
+ # @api public
31
+ def call(relation)
32
+ dataset = relation.dataset.with(attrs: map(&:name), aliases: map(&:alias))
33
+ relation.new(dataset, schema: self)
34
+ end
35
+
36
+ # Rename schema attributes
37
+ #
38
+ # @see Relation#rename
39
+ #
40
+ # @return [Schema] A new schema with renamed attributes
41
+ #
42
+ # @api public
43
+ def rename(mapping)
44
+ super map(&:name).map { |k| { k => k } }.reduce(&:merge).merge(mapping)
45
+ end
46
+
47
+ # Project a schema
48
+ #
49
+ # @see ROM::Schema#project
50
+ # @see Relation#select
51
+ #
52
+ # @return [Schema] A new schema with projected attributes
53
+ #
54
+ # @api public
55
+ def project(*names, &block)
56
+ if block
57
+ super(*(names + ProjectionDSL.new(self).call(&block)))
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ # Project schema so that it only contains primary key
64
+ #
65
+ # @return [Schema]
66
+ #
67
+ # @api private
68
+ def project_pk
69
+ project(*primary_key_names)
70
+ end
71
+
72
+ # Project schema so that it only contains renamed foreign key
73
+ #
74
+ # @return [Schema]
75
+ #
76
+ # @api private
77
+ def project_fk(mapping)
78
+ new(rename(mapping).map(&:foreign_key))
79
+ end
80
+
81
+ # Join with another schema
82
+ #
83
+ # @param [Schema] other The other schema to join with
84
+ #
85
+ # @return [Schema]
86
+ #
87
+ # @api public
88
+ def join(other)
89
+ merge(other.joined)
90
+ end
91
+
92
+ # Return a new schema with all attributes marked as joined
93
+ #
94
+ # @return [Schema]
95
+ #
96
+ # @api public
97
+ def joined
98
+ new(map(&:joined))
99
+ end
100
+
101
+ # Return an empty schema
102
+ #
103
+ # @return [Schema]
104
+ #
105
+ # @api public
106
+ def empty
107
+ new(EMPTY_ARRAY)
108
+ end
109
+
110
+ # @api private
111
+ def finalize_associations!(relations:)
112
+ super do
113
+ associations.map do |definition|
114
+ LDAP::Associations.const_get(definition.type).new(definition, relations)
115
+ end
116
+ end
117
+ end
118
+
119
+ memoize :project_pk, :canonical, :joined
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/initializer'
4
+
5
+ module ROM
6
+ module LDAP
7
+ class Schema < ROM::Schema
8
+
9
+ # @api private
10
+ class AttributesInferrer
11
+
12
+ extend Initializer
13
+
14
+ option :type_builder
15
+ option :attr_class, optional: true
16
+
17
+ # @api private
18
+ def call(schema, gateway)
19
+ dataset = schema.name.dataset
20
+ columns = dataset_attributes(gateway, dataset)
21
+
22
+ inferred = columns.map do |name|
23
+ type = type_builder.call(name, schema.name)
24
+ attr_class.new(type, name: name.to_sym)
25
+ end
26
+
27
+ missing = columns - inferred.map { |attr| attr.meta[:name] }
28
+
29
+ [inferred, missing]
30
+ end
31
+
32
+ # @api private
33
+ def with(new_options)
34
+ self.class.new(options.merge(new_options))
35
+ end
36
+
37
+ private
38
+
39
+ # All possible formatted Entry attribute names.
40
+ #
41
+ # @see Directory#query_attributes
42
+ #
43
+ # @param dataset [String] LDAP filter string / dataset name
44
+ # @param gateway [ROM::LDAP::Gateway]
45
+ #
46
+ # @return [Array<Symbol, String>]
47
+ #
48
+ # @example => [:cn, :dn, :given_name, :mail, :object_class, :sn]
49
+ #
50
+ # @api private
51
+ def dataset_attributes(gateway, dataset)
52
+ gateway[dataset].flat_map(&:keys).uniq.sort
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ class Schema < ROM::Schema
6
+
7
+ # @api public
8
+ class DSL < ROM::Schema::DSL
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/schema/type_builder'
4
+ require 'rom/ldap/schema/attributes_inferrer'
5
+ require 'rom/ldap/attribute'
6
+
7
+ module ROM
8
+ module LDAP
9
+ class Schema < ROM::Schema
10
+
11
+ # @api private
12
+ class Inferrer < ROM::Schema::Inferrer
13
+
14
+ attributes_inferrer ->(schema, gateway, options) do
15
+ builder = TypeBuilder.new(gateway.attribute_types)
16
+ inferrer = AttributesInferrer.new(type_builder: builder, **options)
17
+ inferrer.call(schema, gateway)
18
+ end
19
+
20
+ attr_class LDAP::Attribute
21
+
22
+ option :silent, default: -> { false }
23
+
24
+ option :raise_on_error, default: -> { true }
25
+
26
+ FALLBACK_SCHEMA = {
27
+ attributes: EMPTY_ARRAY,
28
+ indexes: EMPTY_SET
29
+ }.freeze
30
+
31
+ # @api private
32
+ def call(schema, gateway)
33
+ inferred = super
34
+
35
+ { **inferred }
36
+ rescue *CONNECTION_FAILURES => e
37
+ raise ConnectionError, e
38
+ ensure
39
+ FALLBACK_SCHEMA
40
+ end
41
+
42
+ def suppress_errors
43
+ with(raise_on_error: false, silent: true)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/types'
4
+ require 'rom/initializer'
5
+
6
+ module ROM
7
+ module LDAP
8
+ class Schema
9
+
10
+ #
11
+ # ATTRIBUTE TYPE
12
+ #
13
+ # An attribute type is a schema element that correlates an OID and a set of
14
+ # names with an attribute syntax and a set of matching rules.
15
+ #
16
+ # The components of an attribute type definition include:
17
+ #
18
+ # - An OID used to uniquely identify the attribute type.
19
+ # - A set of zero or more names that can be used to more easily reference the attribute type.
20
+ # - An optional equality matching rule that specifies how equality matching
21
+ # should be performed on values of that attribute.
22
+ # If no equality matching rule is specified, then the default equality rule
23
+ # for the associated attribute syntax will be used.
24
+ # If the associated syntax doesn't have a default equality matching rule,
25
+ # then equality operations will not be allowed for that attribute.
26
+ # - An optional ordering matching rule that specifies how ordering operations
27
+ # should be performed on values of that attribute.
28
+ # If no ordering matching rule is specified, then the default ordering rule
29
+ # for the associated attribute syntax will be used.
30
+ # If the associated syntax doesn't have a default ordering matching rule,
31
+ # then ordering operations will not be allowed for that attribute.
32
+ # - An optional substring matching rule that specifies how substring matching
33
+ # should be performed on values of that attribute.
34
+ # If no substring matching rule is specified, then the default substring rule
35
+ # for the associated attribute syntax will be used.
36
+ # If the associated syntax doesn't have a default substring matching rule,
37
+ # then substring operations will not be allowed for that attribute.
38
+ # - An optional syntax OID that specifies the syntax for values of the attribute.
39
+ # If no syntax is specified, then it will default to the directory string syntax.
40
+ # - A flag that indicates whether the attribute is allowed to have multiple values.
41
+ # - An optional attribute usage string indicating the context in which the attribute is to be used.
42
+ # - An optional flag that indicates whether the attribute can be modified by external clients.
43
+ #
44
+ # @see Directory.attributes
45
+ #
46
+ # @see <https://docs.oracle.com/cd/E19450-01/820-6173/def-attribute-type.html>
47
+ #
48
+ # @param attributes [Array<Hash>]
49
+ #
50
+ # @api private
51
+ class TypeBuilder
52
+
53
+ extend Initializer
54
+
55
+ param :attributes
56
+
57
+ # @param attribute_name [String, Symbol]
58
+ #
59
+ # @param schema [Schema] Relation schema object.
60
+ #
61
+ # @api public
62
+ def call(attribute_name, schema)
63
+ attribute = find_attribute(attribute_name)
64
+ primitive = map_type(attribute)
65
+ ruby_type = Types.const_get(primitive)
66
+ read_type = !attribute[:single] ? Types.const_get(Inflector.pluralize(primitive)) : ruby_type
67
+
68
+ ruby_type.meta(
69
+ **attribute,
70
+ source: schema,
71
+ name: attribute_name,
72
+ read: read_type.meta(oid: attribute[:oid])
73
+ )
74
+ end
75
+
76
+ private
77
+
78
+ # Attribute whose formatted name matches the attribute name.
79
+ #
80
+ # @param name [Symbol, String]
81
+ #
82
+ # @return [Hash]
83
+ #
84
+ # @api private
85
+ def find_attribute(name)
86
+ attributes.find { |a| a[:name].eql?(name) } || EMPTY_HASH
87
+ end
88
+
89
+ # Map attribute to Type using known syntax or by inferring from the matchers.
90
+ #
91
+ # @option :syntax [String] attribute syntax
92
+ # @option :matcher [String] attribute matcher
93
+ #
94
+ # @return [String]
95
+ #
96
+ # @api private
97
+ def map_type(syntax: nil, matcher: nil, **)
98
+ by_syntax(syntax) or by_matcher(matcher)
99
+ end
100
+
101
+ # @param oid [String] LDAP Object Identifier
102
+ #
103
+ # @example
104
+ # by_syntax('1.3.6.1.4.1.1466.115.121.1.24') => Time
105
+ #
106
+ # @return [String]
107
+ #
108
+ # @api private
109
+ def by_syntax(oid)
110
+ OID_TYPE_MAP[oid]
111
+ end
112
+
113
+ # @param matcher [String]
114
+ #
115
+ # @return [String]
116
+ #
117
+ # @api private
118
+ def by_matcher(matcher)
119
+ case matcher
120
+ when *STRING_MATCHERS then 'String'
121
+ when *BOOLEAN_MATCHERS then 'Bool'
122
+ when *INTEGER_MATCHERS then 'Integer'
123
+ when *TIME_MATCHERS then 'Time'
124
+ else
125
+ 'String'
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+ end
133
+ end