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,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