Class: Entitlements::Backend::LDAP::Provider

Inherits:
Object
  • Object
show all
Includes:
Contracts::Core
Defined in:
lib/entitlements/backend/ldap/provider.rb

Constant Summary collapse

C =
::Contracts

Instance Method Summary collapse

Methods included from Contracts::Core

common, extended, included

Constructor Details

#initialize(ldap:) ⇒ Provider

Returns a new instance of Provider.



16
17
18
19
20
21
22
23
24
# File 'lib/entitlements/backend/ldap/provider.rb', line 16

def initialize(ldap:)
  @ldap = ldap
  @groups_in_ou_cache = {}

  # Keep track of each LDAP group we have read so we do not end up re-reading
  # certain referenced groups over and over again. This is at a program-wide level. If
  # multiple backends have the same namespace this will probably break.
  Entitlements.cache[:ldap_cache] ||= {}
end

Instance Method Details

#delete(dn) ⇒ Object



86
87
88
89
# File 'lib/entitlements/backend/ldap/provider.rb', line 86

def delete(dn)
  return if ldap.delete(dn)
  raise "Unable to delete LDAP group #{dn.inspect}!"
end

#read(dn) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/entitlements/backend/ldap/provider.rb', line 33

def read(dn)
  Entitlements.cache[:ldap_cache][dn] ||= begin
    Entitlements.logger.debug "Loading group #{dn}"

    # Look up the group by its DN.
    result = ldap.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject)

    # Ensure exactly one result found.
    unless result.size == 1 && result.key?(dn)
      raise Entitlements::Data::Groups::GroupNotFoundError, "No response from LDAP for dn=#{dn}"
    end

    Entitlements::Service::LDAP.entry_to_group(result[dn])
  end
end

#read_all(ou) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/entitlements/backend/ldap/provider.rb', line 56

def read_all(ou)
  @groups_in_ou_cache[ou] ||= begin
    Entitlements.logger.debug "Loading all groups for #{ou}"

    # Find all groups in the OU
    raw = ldap.search(
      base: ou,
      filter: Net::LDAP::Filter.eq("cn", "*"),
      scope: Net::LDAP::SearchScope_SingleLevel
    )

    # Construct a Set of DNs from the result, and also cache the content of the
    # group to avoid a future lookup.
    result = Set.new
    raw.each do |dn, entry|
      Entitlements.cache[:ldap_cache][dn] = Entitlements::Service::LDAP.entry_to_group(entry)
      result.add dn
    end

    # Return that result
    result
  end
end

#upsert(group, override = {}) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/entitlements/backend/ldap/provider.rb', line 98

def upsert(group, override = {})
  members = group.member_strings.map { |ms| ldap.person_dn_format.gsub("%KEY%", ms) }

  attributes = {
    "uniqueMember" => members,
    "description"  => group.description || "",
    "owner"        => [ldap.binddn],
    "objectClass"  => ["groupOfUniqueNames"],
    "cn"           => group.cn
  }.merge(override)
  override.each { |key, val| attributes.delete(key) if val.nil? }

  # LDAP schema does not allow empty groups but does allow a group to be a member of itself.
  # This gets around having to commit a dummy user in case an LDAP group is empty.
  if group.member_strings.empty?
    attributes["uniqueMember"] = [group.dn]
  end

  result = ldap.upsert(dn: group.dn, attributes: attributes)
  return result if result == true
  return false if result.nil?
  raise "Unable to upsert LDAP group #{group.dn.inspect}!"
end