Module: ROM::LDAP::Directory::Operations

Included in:
ROM::LDAP::Directory
Defined in:
lib/rom/ldap/directory/operations.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(klass) ⇒ Object



10
11
12
13
14
# File 'lib/rom/ldap/directory/operations.rb', line 10

def self.included(klass)
  klass.class_eval do
    extend Dry::Core::Cache
  end
end

Instance Method Details

#add(tuple) ⇒ Entry, FalseClass

Returns created LDAP entry or false.

Parameters:

  • tuple (Hash)

    tuple using formatted attribute names.

Returns:

  • (Entry, FalseClass)

    created LDAP entry or false.

Raises:



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/rom/ldap/directory/operations.rb', line 133

def add(tuple)
  dn    = tuple.delete(:dn)
  attrs = canonicalise(tuple)
  raise(DistinguishedNameError, 'DN is required') unless dn

  log(__callee__, dn)

  pdu = client.add(dn: dn, attrs: attrs)

  pdu.success? ? find(dn) : pdu.success?
end

#base_totalInteger

Count all entries under the search base.

Returns:

  • (Integer)


123
124
125
# File 'lib/rom/ldap/directory/operations.rb', line 123

def base_total
  query(base: base, attributes: %w[objectClass], attributes_only: true).count
end

#bind_as(filter:, password:) ⇒ Boolean

Parameters:

  • :filter (Hash)

    a customizable set of options

  • :password (Hash)

    a customizable set of options

Returns:

  • (Boolean)


85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rom/ldap/directory/operations.rb', line 85

def bind_as(filter:, password:)
  if (entity = query(filter: filter, max: 1).first)
    password = password.call if password.respond_to?(:call)

    pdu = client.bind(username: entity.dn, password: password)
    pdu.success?
  else
    false
  end
rescue BindError
  false
end

#by_dn(dn) ⇒ Array<Entry>

Return all attributes for a distinguished name.

Parameters:

  • dn (String)

Returns:

Raises:



71
72
73
74
75
# File 'lib/rom/ldap/directory/operations.rb', line 71

def by_dn(dn)
  raise(DistinguishedNameError, 'DN is required') unless dn

  query(base: dn, max: 1, attributes: ALL_ATTRS)
end

#debug(pdu) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/rom/ldap/directory/operations.rb', line 56

def debug(pdu)
  return unless ::ENV['DEBUG']

  logger.debug(pdu.advice) if pdu&.advice
  logger.debug(pdu.message) if pdu&.message
  logger.debug(pdu.info) if pdu&.failure?
end

#delete(dn) ⇒ Entry, FalseClass

Returns deleted LDAP entry or false.

Parameters:

  • dn (String)

    distinguished name.

Returns:

  • (Entry, FalseClass)

    deleted LDAP entry or false.



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/rom/ldap/directory/operations.rb', line 224

def delete(dn)
  log(__callee__, dn)
  entry = find(dn)

  pdu = if pruneable?
          controls = [OID[:delete_tree]]
          client.delete(dn: dn, controls: controls)
        else
          client.delete(dn: dn)
        end

  pdu.success? ? entry : pdu.success?
end

#find(dn) ⇒ Array<Hash>, Hash

Tuple(s) by dn

Parameters:

  • dn (String)

    distinguished name

Returns:

  • (Array<Hash>, Hash)

Raises:



211
212
213
214
215
216
# File 'lib/rom/ldap/directory/operations.rb', line 211

def find(dn)
  entry = by_dn(dn)
  raise(DistinguishedNameError, 'DN not found') unless entry

  entry.one? ? entry.first : entry
end

#modify(dn, tuple) ⇒ Entry, FalseClass

client#rename > client#password_modify > client#update

Parameters:

  • dn (String)

    distinguished name.

  • tuple (Hash)

    tuple using formatted attribute names.

Returns:

  • (Entry, FalseClass)

    updated LDAP entry or false.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rom/ldap/directory/operations.rb', line 154

def modify(dn, tuple)
  log(__callee__, dn)

  # entry = find(dn)

  new_dn = tuple.delete(:dn)
  attrs  = canonicalise(tuple)

  rdn_attr, rdn_val = get_rdn(dn).split('=')

  # 1. Move rename
  if new_dn
    new_rdn = get_rdn(new_dn)
    parent  = get_parent_dn(new_dn)

    new_rdn_attr, new_rdn_val = new_rdn.split('=')

    replace = rdn_attr.eql?(new_rdn_attr)

    pdu = client.rename(dn: dn, rdn: new_rdn, replace: replace, superior: parent)

    if pdu.success?
      dn, rdn_attr, rdn_val = new_dn, new_rdn_attr, new_rdn_val
    end
  end

  # 2. Change password
  if attrs.key?('userPassword')
    new_pwd = attrs.delete('userPassword')
    entry   = find(dn)
    old_pwd = entry['userPassword']

    pdu = client.password_modify(dn, old_pwd: old_pwd, new_pwd: new_pwd)
  end

  # 3. Edit attributes
  unless attrs.empty?

    # Adding to RDN values?
    if attrs.key?(rdn_attr) && !attrs.key?(rdn_val)
      attrs[rdn_attr] = Array(attrs[rdn_attr]).unshift(rdn_val)
    end

    pdu = client.update(dn: dn, ops: attrs.to_a)
  end

  pdu.success? ? find(dn) : pdu.success?
end

#query(filter: DEFAULT_FILTER, **options) ⇒ Array<Entry>

Use connection to communicate with server. If :base is passed, it overwrites the default base keyword.

Parameters:

  • options (Hash)

    @see Connection::SearchRequest

  • :filter (Hash)

    a customizable set of options

Returns:

  • (Array<Entry>)

    Formatted hash like objects.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/rom/ldap/directory/operations.rb', line 28

def query(filter: DEFAULT_FILTER, **options)
  set, counter = [], 0

  # TODO: pageable and search referrals
  params = {
    base: base,
    expression: to_expression(filter),
    **options
    # paged: pageable?

    # return_refs: true
    # https://tools.ietf.org/html/rfc4511#section-4.5.3
  }

  # pdu = client.search(params) do |search_referrals: |
  pdu = client.search(params) do |dn, attributes|
    counter += 1
    logger.debug("#{counter}: #{dn}") if ::ENV['DEBUG']

    set << entity = Entry.new(dn, attributes)
    yield(entity) if block_given?
  end

  debug(pdu)

  set
end

#query_attributes(filter) ⇒ Array<Entry>

Used by gateway to infer schema at boot.

Limited to 1000 and cached.

Parameters:

  • filter (String)

    dataset schema filter

Returns:



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rom/ldap/directory/operations.rb', line 106

def query_attributes(filter)
  fetch_or_store(base, filter) do
    query(
      filter: filter,
      base: base,
      max: 1_000, # attribute sample size
      attributes_only: true
      # paged: false
    )
  end
end