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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +251 -0
- data/CONTRIBUTING.md +18 -0
- data/README.md +172 -0
- data/TODO.md +33 -0
- data/config/responses.yml +328 -0
- data/lib/dry/monitor/ldap/colorizers/default.rb +17 -0
- data/lib/dry/monitor/ldap/colorizers/rouge.rb +31 -0
- data/lib/dry/monitor/ldap/logger.rb +58 -0
- data/lib/rom-ldap.rb +1 -0
- data/lib/rom/ldap.rb +22 -0
- data/lib/rom/ldap/alias.rb +30 -0
- data/lib/rom/ldap/associations.rb +6 -0
- data/lib/rom/ldap/associations/core.rb +23 -0
- data/lib/rom/ldap/associations/many_to_many.rb +18 -0
- data/lib/rom/ldap/associations/many_to_one.rb +22 -0
- data/lib/rom/ldap/associations/one_to_many.rb +32 -0
- data/lib/rom/ldap/associations/one_to_one.rb +19 -0
- data/lib/rom/ldap/associations/self_ref.rb +35 -0
- data/lib/rom/ldap/attribute.rb +327 -0
- data/lib/rom/ldap/client.rb +185 -0
- data/lib/rom/ldap/client/authentication.rb +118 -0
- data/lib/rom/ldap/client/operations.rb +233 -0
- data/lib/rom/ldap/commands.rb +6 -0
- data/lib/rom/ldap/commands/create.rb +41 -0
- data/lib/rom/ldap/commands/delete.rb +17 -0
- data/lib/rom/ldap/commands/update.rb +35 -0
- data/lib/rom/ldap/constants.rb +193 -0
- data/lib/rom/ldap/dataset.rb +286 -0
- data/lib/rom/ldap/dataset/conversion.rb +62 -0
- data/lib/rom/ldap/dataset/dsl.rb +299 -0
- data/lib/rom/ldap/dataset/persistence.rb +44 -0
- data/lib/rom/ldap/directory.rb +126 -0
- data/lib/rom/ldap/directory/capabilities.rb +71 -0
- data/lib/rom/ldap/directory/entry.rb +200 -0
- data/lib/rom/ldap/directory/env.rb +155 -0
- data/lib/rom/ldap/directory/operations.rb +282 -0
- data/lib/rom/ldap/directory/password.rb +122 -0
- data/lib/rom/ldap/directory/root.rb +187 -0
- data/lib/rom/ldap/directory/tokenization.rb +66 -0
- data/lib/rom/ldap/directory/transactions.rb +31 -0
- data/lib/rom/ldap/directory/vendors/active_directory.rb +129 -0
- data/lib/rom/ldap/directory/vendors/apache_ds.rb +27 -0
- data/lib/rom/ldap/directory/vendors/e_directory.rb +16 -0
- data/lib/rom/ldap/directory/vendors/open_directory.rb +12 -0
- data/lib/rom/ldap/directory/vendors/open_dj.rb +25 -0
- data/lib/rom/ldap/directory/vendors/open_ldap.rb +35 -0
- data/lib/rom/ldap/directory/vendors/three_eight_nine.rb +16 -0
- data/lib/rom/ldap/directory/vendors/unknown.rb +22 -0
- data/lib/rom/ldap/dsl.rb +76 -0
- data/lib/rom/ldap/errors.rb +47 -0
- data/lib/rom/ldap/expression.rb +77 -0
- data/lib/rom/ldap/expression_encoder.rb +174 -0
- data/lib/rom/ldap/extensions.rb +50 -0
- data/lib/rom/ldap/extensions/active_support_notifications.rb +26 -0
- data/lib/rom/ldap/extensions/compatibility.rb +11 -0
- data/lib/rom/ldap/extensions/dsml.rb +165 -0
- data/lib/rom/ldap/extensions/msgpack.rb +23 -0
- data/lib/rom/ldap/extensions/optimised_json.rb +25 -0
- data/lib/rom/ldap/extensions/rails_log_subscriber.rb +38 -0
- data/lib/rom/ldap/formatter.rb +26 -0
- data/lib/rom/ldap/functions.rb +207 -0
- data/lib/rom/ldap/gateway.rb +145 -0
- data/lib/rom/ldap/ldif.rb +74 -0
- data/lib/rom/ldap/ldif/exporter.rb +77 -0
- data/lib/rom/ldap/ldif/importer.rb +95 -0
- data/lib/rom/ldap/mapper_compiler.rb +19 -0
- data/lib/rom/ldap/matchers.rb +69 -0
- data/lib/rom/ldap/message_queue.rb +7 -0
- data/lib/rom/ldap/oid.rb +101 -0
- data/lib/rom/ldap/parsers/abstract_syntax.rb +91 -0
- data/lib/rom/ldap/parsers/attribute.rb +290 -0
- data/lib/rom/ldap/parsers/filter_syntax.rb +133 -0
- data/lib/rom/ldap/pdu.rb +285 -0
- data/lib/rom/ldap/plugin/pagination.rb +145 -0
- data/lib/rom/ldap/plugins.rb +7 -0
- data/lib/rom/ldap/projection_dsl.rb +38 -0
- data/lib/rom/ldap/relation.rb +135 -0
- data/lib/rom/ldap/relation/exporting.rb +72 -0
- data/lib/rom/ldap/relation/reading.rb +461 -0
- data/lib/rom/ldap/relation/writing.rb +64 -0
- data/lib/rom/ldap/responses.rb +17 -0
- data/lib/rom/ldap/restriction_dsl.rb +45 -0
- data/lib/rom/ldap/schema.rb +123 -0
- data/lib/rom/ldap/schema/attributes_inferrer.rb +59 -0
- data/lib/rom/ldap/schema/dsl.rb +13 -0
- data/lib/rom/ldap/schema/inferrer.rb +50 -0
- data/lib/rom/ldap/schema/type_builder.rb +133 -0
- data/lib/rom/ldap/scope.rb +19 -0
- data/lib/rom/ldap/search_request.rb +249 -0
- data/lib/rom/ldap/socket.rb +210 -0
- data/lib/rom/ldap/tasks/ldap.rake +103 -0
- data/lib/rom/ldap/tasks/ldif.rake +80 -0
- data/lib/rom/ldap/transaction.rb +29 -0
- data/lib/rom/ldap/type_map.rb +88 -0
- data/lib/rom/ldap/types.rb +158 -0
- data/lib/rom/ldap/version.rb +17 -0
- data/lib/rom/plugins/relation/ldap/active_directory.rb +182 -0
- data/lib/rom/plugins/relation/ldap/auto_restrictions.rb +69 -0
- data/lib/rom/plugins/relation/ldap/e_directory.rb +27 -0
- data/lib/rom/plugins/relation/ldap/instrumentation.rb +35 -0
- data/lib/rouge/lexers/ldap.rb +72 -0
- data/lib/rouge/themes/ldap.rb +49 -0
- metadata +231 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ber'
|
|
4
|
+
|
|
5
|
+
require 'rom/initializer'
|
|
6
|
+
require 'rom/ldap/message_queue'
|
|
7
|
+
require 'rom/ldap/pdu'
|
|
8
|
+
require 'rom/ldap/socket'
|
|
9
|
+
require 'rom/ldap/client/operations'
|
|
10
|
+
require 'rom/ldap/client/authentication'
|
|
11
|
+
|
|
12
|
+
module ROM
|
|
13
|
+
module LDAP
|
|
14
|
+
#
|
|
15
|
+
# Uses socket to read and write using BER encoding.
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
class Client
|
|
19
|
+
|
|
20
|
+
using ::BER
|
|
21
|
+
|
|
22
|
+
extend Initializer
|
|
23
|
+
|
|
24
|
+
# @!attribute [r] host
|
|
25
|
+
# @return [String]
|
|
26
|
+
option :host, reader: :private, type: Types::Strict::String
|
|
27
|
+
|
|
28
|
+
option :port, reader: :private, type: Types::Strict::Integer
|
|
29
|
+
option :path, reader: :private, type: Types::Strict::String
|
|
30
|
+
option :auth, reader: :private, type: Types::Strict::Hash, optional: true
|
|
31
|
+
option :ssl, reader: :private, type: Types::Strict::Hash, optional: true
|
|
32
|
+
option :queue, default: -> { MessageQueue }
|
|
33
|
+
|
|
34
|
+
include Operations
|
|
35
|
+
include Authentication
|
|
36
|
+
|
|
37
|
+
attr_reader :socket
|
|
38
|
+
|
|
39
|
+
# Create connection (encrypted) and authenticate.
|
|
40
|
+
#
|
|
41
|
+
# @yield [Socket]
|
|
42
|
+
#
|
|
43
|
+
# @raise [BindError, SecureBindError]
|
|
44
|
+
def open
|
|
45
|
+
unless alive?
|
|
46
|
+
@socket = Socket.new(options).call
|
|
47
|
+
|
|
48
|
+
# tls
|
|
49
|
+
if ssl
|
|
50
|
+
start_tls
|
|
51
|
+
sasl_bind # (mechanism:, credentials:, challenge:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
bind(auth) unless auth.nil? # simple auth
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
yield(@socket)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
#
|
|
62
|
+
def closed?
|
|
63
|
+
socket.nil? || (socket.is_a?(::Socket) && socket.closed?)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [NilClass]
|
|
67
|
+
#
|
|
68
|
+
def close
|
|
69
|
+
return if socket.nil?
|
|
70
|
+
|
|
71
|
+
socket.close
|
|
72
|
+
@socket = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
#
|
|
77
|
+
def alive?
|
|
78
|
+
return false if closed?
|
|
79
|
+
|
|
80
|
+
if IO.select([socket], nil, nil, 0)
|
|
81
|
+
begin
|
|
82
|
+
!socket.eof?
|
|
83
|
+
rescue StandardError
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
rescue IOError
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# @see BER
|
|
96
|
+
#
|
|
97
|
+
# @param symbol [Symbol]
|
|
98
|
+
#
|
|
99
|
+
# @return [Integer]
|
|
100
|
+
def pdu_lookup(symbol)
|
|
101
|
+
::BER.fetch(:response, symbol)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Read from socket and wrap in PDU class.
|
|
105
|
+
#
|
|
106
|
+
# @return [PDU, NilClass]
|
|
107
|
+
def read
|
|
108
|
+
open do |socket|
|
|
109
|
+
return unless (ber_object = socket.read_ber)
|
|
110
|
+
|
|
111
|
+
PDU.new(*ber_object)
|
|
112
|
+
end
|
|
113
|
+
rescue Errno::ECONNRESET
|
|
114
|
+
close
|
|
115
|
+
retry
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Write to socket.
|
|
119
|
+
#
|
|
120
|
+
# @api private
|
|
121
|
+
def write(request, message_id, controls = nil)
|
|
122
|
+
open do |socket|
|
|
123
|
+
packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
|
|
124
|
+
socket.write(packet)
|
|
125
|
+
socket.flush
|
|
126
|
+
end
|
|
127
|
+
rescue Errno::EPIPE, IOError
|
|
128
|
+
close
|
|
129
|
+
retry
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @return [PDU]
|
|
133
|
+
#
|
|
134
|
+
# @api private
|
|
135
|
+
def from_queue(message_id)
|
|
136
|
+
if (pdu = queue[message_id].shift)
|
|
137
|
+
return pdu
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
while (pdu = read)
|
|
141
|
+
return pdu if pdu.message_id.eql?(message_id)
|
|
142
|
+
|
|
143
|
+
queue[pdu.message_id].push(pdu)
|
|
144
|
+
next
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
pdu
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Increment the message counter.
|
|
151
|
+
#
|
|
152
|
+
# @return [Integer]
|
|
153
|
+
#
|
|
154
|
+
# @api private
|
|
155
|
+
def next_msgid
|
|
156
|
+
@msgid ||= 0
|
|
157
|
+
@msgid += 1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Persist changes to the server and return response object.
|
|
161
|
+
# Enable stdout debugging with DEBUG=y.
|
|
162
|
+
#
|
|
163
|
+
# @return [PDU]
|
|
164
|
+
#
|
|
165
|
+
# @raise [ResponseError]
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
def submit(type, request, controls = nil)
|
|
169
|
+
message_id = next_msgid
|
|
170
|
+
|
|
171
|
+
write(request, message_id, controls)
|
|
172
|
+
|
|
173
|
+
pdu = from_queue(message_id)
|
|
174
|
+
|
|
175
|
+
if pdu&.app_tag == pdu_lookup(type)
|
|
176
|
+
puts pdu.advice if ENV['DEBUG'] && pdu.advice && !pdu.advice.empty?
|
|
177
|
+
pdu
|
|
178
|
+
else
|
|
179
|
+
raise(ResponseError, "Invalid #{type}")
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Lint/SuppressedException
|
|
4
|
+
begin
|
|
5
|
+
# TODO: Windows NTLM authentication
|
|
6
|
+
# @see https://github.com/winrb/rubyntlm
|
|
7
|
+
require 'rubyntlm'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# ntlm_bind
|
|
10
|
+
end
|
|
11
|
+
# rubocop:enable Lint/SuppressedException
|
|
12
|
+
|
|
13
|
+
module ROM
|
|
14
|
+
module LDAP
|
|
15
|
+
# @api private
|
|
16
|
+
class Client
|
|
17
|
+
|
|
18
|
+
using ::BER
|
|
19
|
+
|
|
20
|
+
# Adds authentication capability to the client.
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
module Authentication
|
|
24
|
+
#
|
|
25
|
+
# The Bind request is defined as follows:
|
|
26
|
+
#
|
|
27
|
+
# BindRequest ::= [APPLICATION 0] SEQUENCE {
|
|
28
|
+
# version INTEGER (1 .. 127),
|
|
29
|
+
# name LDAPDN,
|
|
30
|
+
# authentication AuthenticationChoice }
|
|
31
|
+
#
|
|
32
|
+
# AuthenticationChoice ::= CHOICE {
|
|
33
|
+
# simple [0] OCTET STRING,
|
|
34
|
+
# -- 1 and 2 reserved
|
|
35
|
+
# sasl [3] SaslCredentials,
|
|
36
|
+
# ... }
|
|
37
|
+
#
|
|
38
|
+
# SaslCredentials ::= SEQUENCE {
|
|
39
|
+
# mechanism LDAPString,
|
|
40
|
+
# credentials OCTET STRING OPTIONAL }
|
|
41
|
+
#
|
|
42
|
+
# @see https://tools.ietf.org/html/rfc4511#section-4.2
|
|
43
|
+
# @see https://tools.ietf.org/html/rfc4513
|
|
44
|
+
#
|
|
45
|
+
#
|
|
46
|
+
# @option :username [String]
|
|
47
|
+
#
|
|
48
|
+
# @option :password [String]
|
|
49
|
+
#
|
|
50
|
+
# @return [PDU] result object
|
|
51
|
+
#
|
|
52
|
+
# @raise [BindError]
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
def bind(username:, password:)
|
|
56
|
+
request_type = pdu_lookup(:bind_request)
|
|
57
|
+
|
|
58
|
+
request = [
|
|
59
|
+
3.to_ber,
|
|
60
|
+
username.to_ber,
|
|
61
|
+
password.to_ber_contextspecific(0)
|
|
62
|
+
].to_ber_appsequence(request_type)
|
|
63
|
+
|
|
64
|
+
pdu = submit(:bind_result, request)
|
|
65
|
+
raise(BindError, username) if pdu.failure?
|
|
66
|
+
|
|
67
|
+
pdu
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
#
|
|
72
|
+
# @return [PDU] result object
|
|
73
|
+
#
|
|
74
|
+
# @api private
|
|
75
|
+
def start_tls
|
|
76
|
+
request_type = pdu_lookup(:extended_request)
|
|
77
|
+
|
|
78
|
+
request = [
|
|
79
|
+
OID[:start_tls].to_ber_contextspecific(0)
|
|
80
|
+
].to_ber_appsequence(request_type)
|
|
81
|
+
|
|
82
|
+
submit(:extended_response, request)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# @return
|
|
87
|
+
#
|
|
88
|
+
# @raise [SecureBindError]
|
|
89
|
+
#
|
|
90
|
+
# @api private
|
|
91
|
+
def sasl_bind(mechanism:, credentials:, challenge:)
|
|
92
|
+
request_type = pdu_lookup(:bind_request)
|
|
93
|
+
n = 0
|
|
94
|
+
|
|
95
|
+
loop do
|
|
96
|
+
sasl = [
|
|
97
|
+
mechanism.to_ber,
|
|
98
|
+
credentials.to_ber
|
|
99
|
+
].to_ber_contextspecific(3)
|
|
100
|
+
|
|
101
|
+
request = [
|
|
102
|
+
3.to_ber,
|
|
103
|
+
EMPTY_STRING.to_ber,
|
|
104
|
+
sasl
|
|
105
|
+
].to_ber_appsequence(request_type)
|
|
106
|
+
|
|
107
|
+
raise SecureBindError, 'sasl-challenge overflow' if (n += 1) > 10
|
|
108
|
+
|
|
109
|
+
pdu = submit(:bind_request, request)
|
|
110
|
+
|
|
111
|
+
credentials = challenge.call(pdu.result_server_sasl_creds)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rom/ldap/search_request'
|
|
4
|
+
|
|
5
|
+
module ROM
|
|
6
|
+
module LDAP
|
|
7
|
+
# @api private
|
|
8
|
+
class Client
|
|
9
|
+
|
|
10
|
+
using ::BER
|
|
11
|
+
|
|
12
|
+
# Adds entry creation capability to the connection.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
module Operations
|
|
16
|
+
# Connection Search Operation
|
|
17
|
+
#
|
|
18
|
+
# @see https://tools.ietf.org/html/rfc4511#section-4.5.3
|
|
19
|
+
# @see https://tools.ietf.org/html/rfc2696
|
|
20
|
+
#
|
|
21
|
+
# @todo Write spec for yielding search_referrals
|
|
22
|
+
#
|
|
23
|
+
# @yield [Entry]
|
|
24
|
+
# @yield [Hash] :search_referrals
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
def search(return_refs: true, **params)
|
|
28
|
+
search_request = SearchRequest.new(params)
|
|
29
|
+
request_type = pdu_lookup(:search_request)
|
|
30
|
+
result_pdu = nil
|
|
31
|
+
|
|
32
|
+
more_pages = false
|
|
33
|
+
paged_results_cookie = [126, EMPTY_STRING]
|
|
34
|
+
# paged_results_cookie = [10, EMPTY_STRING]
|
|
35
|
+
|
|
36
|
+
request = search_request.parts.to_ber_appsequence(request_type)
|
|
37
|
+
controls = search_request.controls
|
|
38
|
+
message_id = next_msgid
|
|
39
|
+
|
|
40
|
+
loop do
|
|
41
|
+
write(request, message_id, controls)
|
|
42
|
+
|
|
43
|
+
result_pdu = nil
|
|
44
|
+
controls = EMPTY_ARRAY
|
|
45
|
+
|
|
46
|
+
while (pdu = from_queue(message_id))
|
|
47
|
+
case pdu.app_tag
|
|
48
|
+
when pdu_lookup(:search_returned_data) # 4
|
|
49
|
+
yield(pdu.search_entry)
|
|
50
|
+
|
|
51
|
+
when pdu_lookup(:search_result) # 5
|
|
52
|
+
result_pdu = pdu
|
|
53
|
+
controls = pdu.result_controls
|
|
54
|
+
yield(search_referrals: pdu.search_referrals) if return_refs && pdu.search_referral?
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
when pdu_lookup(:search_result_referral) # 19
|
|
58
|
+
yield(search_referrals: pdu.search_referrals) if return_refs
|
|
59
|
+
|
|
60
|
+
else
|
|
61
|
+
raise ResponseTypeInvalidError, "invalid response-type in search: #{pdu.app_tag}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if result_pdu&.success?
|
|
66
|
+
controls.each do |c|
|
|
67
|
+
next if c.oid != OID[:paged_results]
|
|
68
|
+
|
|
69
|
+
next if c.value&.empty?
|
|
70
|
+
|
|
71
|
+
# [ 0, "" ]
|
|
72
|
+
_int, cookie = c.value.read_ber
|
|
73
|
+
|
|
74
|
+
# next page of results
|
|
75
|
+
#
|
|
76
|
+
unless cookie&.empty?
|
|
77
|
+
|
|
78
|
+
# cookie => "\u0001\u0000\u0000"
|
|
79
|
+
# cookie.read_ber => true
|
|
80
|
+
|
|
81
|
+
paged_results_cookie[1] = cookie
|
|
82
|
+
more_pages = true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
break unless more_pages
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
result_pdu
|
|
91
|
+
ensure
|
|
92
|
+
queue.delete(message_id)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# @option :dn [String] distinguished name
|
|
97
|
+
# @option :attrs [Hash]
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
def add(dn:, attrs:)
|
|
101
|
+
request_type = pdu_lookup(:add_request)
|
|
102
|
+
|
|
103
|
+
ber_attrs = attrs.each_with_object([]) do |(k, v), attributes|
|
|
104
|
+
ber_values = values_to_ber_set(v)
|
|
105
|
+
attributes << [k.to_s.to_ber, ber_values].to_ber_sequence
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
request = [
|
|
109
|
+
dn.to_ber,
|
|
110
|
+
ber_attrs.to_ber_sequence
|
|
111
|
+
].to_ber_appsequence(request_type)
|
|
112
|
+
|
|
113
|
+
submit(:add_response, request)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @option :dn [String] distinguished name
|
|
117
|
+
#
|
|
118
|
+
# @option :controls [Array<String>] e.g. DELETE_TREE
|
|
119
|
+
#
|
|
120
|
+
# @api private
|
|
121
|
+
def delete(dn:, controls: nil)
|
|
122
|
+
request_type = pdu_lookup(:delete_request)
|
|
123
|
+
request = dn.to_ber_application_string(request_type)
|
|
124
|
+
|
|
125
|
+
if controls
|
|
126
|
+
submit(:delete_response, request, controls.to_ber_control)
|
|
127
|
+
else
|
|
128
|
+
submit(:delete_response, request)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @option :dn [String] distinguished name
|
|
133
|
+
#
|
|
134
|
+
# @option :ops [Array<Mixed>] operation ast
|
|
135
|
+
#
|
|
136
|
+
# @return [PDU] result object
|
|
137
|
+
#
|
|
138
|
+
# @api private
|
|
139
|
+
def update(dn:, ops:)
|
|
140
|
+
request_type = pdu_lookup(:modify_request)
|
|
141
|
+
operations = modify_ops(ops)
|
|
142
|
+
|
|
143
|
+
request = [
|
|
144
|
+
dn.to_ber,
|
|
145
|
+
operations.to_ber_sequence
|
|
146
|
+
].to_ber_appsequence(request_type)
|
|
147
|
+
|
|
148
|
+
submit(:modify_response, request)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# TODO: spec rename and use by relations
|
|
152
|
+
#
|
|
153
|
+
# @option :dn [String] current distinguished name
|
|
154
|
+
#
|
|
155
|
+
# @option :rdn [String] new relative distinguished name
|
|
156
|
+
#
|
|
157
|
+
# @option :replace [TrueClass] replace existing rdn
|
|
158
|
+
#
|
|
159
|
+
# @option :superior [String] new parent dn
|
|
160
|
+
#
|
|
161
|
+
# @return [PDU] result object
|
|
162
|
+
#
|
|
163
|
+
# @api public
|
|
164
|
+
def rename(dn:, rdn:, replace: false, superior: nil)
|
|
165
|
+
request_type = pdu_lookup(:modify_rdn_request)
|
|
166
|
+
|
|
167
|
+
request = [dn, rdn, replace].map { |a| a.to_ber } # &:to_ber
|
|
168
|
+
|
|
169
|
+
request << superior.to_ber_contextspecific(0) if superior
|
|
170
|
+
|
|
171
|
+
request = request.to_ber_appsequence(request_type)
|
|
172
|
+
|
|
173
|
+
submit(:modify_rdn_response, request)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Password should have a minimum of 5 characters.
|
|
177
|
+
#
|
|
178
|
+
# @see http://tools.ietf.org/html/rfc3062
|
|
179
|
+
#
|
|
180
|
+
# @option :dn [String] distinguished name
|
|
181
|
+
#
|
|
182
|
+
# @option :old_pwd [String] current password (optional for admin reset)
|
|
183
|
+
#
|
|
184
|
+
# @option :new_pwd [String] replacement password
|
|
185
|
+
#
|
|
186
|
+
# @return [PDU] result object
|
|
187
|
+
#
|
|
188
|
+
# @api public
|
|
189
|
+
def password_modify(dn:, old_pwd: nil, new_pwd:)
|
|
190
|
+
request_type = pdu_lookup(:extended_request)
|
|
191
|
+
context = OID[:password_modify].to_ber_contextspecific(0)
|
|
192
|
+
|
|
193
|
+
payload = [dn.to_ber(0x80)]
|
|
194
|
+
payload << old_pwd.to_ber(0x81) if old_pwd
|
|
195
|
+
payload << new_pwd.to_ber(0x82)
|
|
196
|
+
payload = payload.to_ber_sequence.to_ber(0x81)
|
|
197
|
+
|
|
198
|
+
request = [context, payload].to_ber_appsequence(request_type)
|
|
199
|
+
|
|
200
|
+
submit(:extended_response, request)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
# Encode (replace) operation AST to BER.
|
|
206
|
+
# Operation tokens are add=0, delete=1 and replace=2.
|
|
207
|
+
#
|
|
208
|
+
# @param operations [Array]
|
|
209
|
+
#
|
|
210
|
+
# @return [Array] BER encoded operations
|
|
211
|
+
def modify_ops(operations = EMPTY_ARRAY)
|
|
212
|
+
operations.each_with_object([]) do |(attribute, values), ops|
|
|
213
|
+
payload = [
|
|
214
|
+
attribute.to_s.to_ber,
|
|
215
|
+
values_to_ber_set(values)
|
|
216
|
+
].to_ber_sequence
|
|
217
|
+
|
|
218
|
+
ops << [2.to_ber_enumerated, payload].to_ber
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# @param values [String, Array<String>]
|
|
223
|
+
#
|
|
224
|
+
# @return [String] Encoding:ASCII-8BIT
|
|
225
|
+
#
|
|
226
|
+
def values_to_ber_set(values)
|
|
227
|
+
Array(values).map { |v| v&.to_ber }.to_ber_set
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|