group.rb 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Ldap
  3. # Class for handling LDAP Groups.
  4. class Group
  5. include Ldap::FilterLookup
  6. # Returns the uid attribute.
  7. #
  8. # @example
  9. # Ldap::Group.uid_attribute
  10. #
  11. # @return [String] The uid attribute.
  12. def self.uid_attribute
  13. 'dn'
  14. end
  15. # Initializes a wrapper around Net::LDAP and ::Ldap to handle LDAP groups.
  16. #
  17. # @param [Hash] config the configuration for establishing a LDAP connection. Default is Setting 'ldap_config'.
  18. # @option config [String] :uid_attribute The uid attribute. Default is determined automatically.
  19. # @option config [String] :filter The filter for LDAP groups. Default is determined automatically.
  20. # @param ldap [Ldap] An optional existing Ldap class instance. Default is a new connection with given configuration.
  21. #
  22. # @example
  23. # ldap_group = Ldap::Group.new
  24. #
  25. # @return [nil]
  26. def initialize(config = nil, ldap: nil)
  27. @ldap = ldap || ::Ldap.new(config)
  28. handle_config(config)
  29. end
  30. # Lists available LDAP groups.
  31. #
  32. # @param filter [String] The filter for listing groups. Default is initialization parameter.
  33. # @param base_dn [String] The applied base DN for listing groups. Default is Ldap#base_dn.
  34. #
  35. # @example
  36. # ldap_group.list
  37. # #=> {"cn=zamamd role admin,ou=zamamd groups,ou=test,dc=domain,dc=tld"=>"cn=zamamd role admin,ou=zamamd groups,ou=test,dc=domain,dc=tld", ...}
  38. #
  39. # @return [Hash{String=>String}] List of available LDAP groups.
  40. def list(filter: nil, base_dn: nil)
  41. filter ||= filter()
  42. # don't start a search if no filter was found
  43. return {} if filter.blank?
  44. groups = {}
  45. @ldap.search(filter, base: base_dn, attributes: %w[dn]) do |entry|
  46. groups[entry.dn.downcase] = entry.dn.downcase
  47. end
  48. groups
  49. end
  50. # Creates a mapping for user DN and local role IDs based on a given group DN to local role ID mapping.
  51. #
  52. # @param mapping [Hash{String=>String}] The group DN to local role mapping.
  53. # @param filter [String] The filter for finding groups. Default is initialization parameter.
  54. #
  55. # @example
  56. # mapping = {"cn=access control assistance operators,cn=builtin,dc=domain,dc=tld"=>"1", ...}
  57. # ldap_group.user_roles(mapping)
  58. # #=> {"cn=s-1-5-11,cn=foreignsecurityprincipals,dc=domain,dc=tld"=>[1, 2], ...}
  59. #
  60. # @return [Hash{String=>Array<Number>}] The user DN to local role IDs mapping.
  61. def user_roles(mapping, filter: nil)
  62. filter ||= filter()
  63. result = {}
  64. @ldap.search(filter, attributes: %w[dn member memberuid uniquemember]) do |entry|
  65. roles = mapping[entry.dn.downcase]
  66. next if roles.blank?
  67. members = group_user_dns(entry)
  68. next if members.blank?
  69. members.each do |user_dn|
  70. user_dn_key = user_dn.downcase
  71. roles.each do |role|
  72. role = role.to_i
  73. result[user_dn_key] ||= []
  74. next if result[user_dn_key].include?(role)
  75. result[user_dn_key].push(role)
  76. end
  77. end
  78. end
  79. result
  80. end
  81. # The active filter of the instance. If none give on initialization an automatic lookup is performed.
  82. #
  83. # @example
  84. # ldap_group.filter
  85. # #=> '(objectClass=group)'
  86. #
  87. # @return [String, nil] The active or found filter or nil if none could be found.
  88. def filter
  89. @filter ||= lookup_filter(['(objectClass=groupOfUniqueNames)', '(objectClass=groupOfNames)', '(objectClass=group)', '(objectClass=posixgroup)', '(objectClass=organization)'])
  90. end
  91. # The active uid attribute of the instance. If none give on initialization an automatic lookup is performed.
  92. #
  93. # @example
  94. # ldap_group.uid_attribute
  95. # #=> 'dn'
  96. #
  97. # @return [String, nil] The active or found uid attribute or nil if none could be found.
  98. def uid_attribute
  99. @uid_attribute ||= self.class.uid_attribute
  100. end
  101. private
  102. def handle_config(config)
  103. return if config.blank?
  104. @uid_attribute = config[:uid_attribute]
  105. @filter = config[:filter]
  106. @user_filter = config[:user_filter]
  107. end
  108. def group_user_dns(entry)
  109. return entry[:member] if entry[:member].present?
  110. # workaround for windows ad's with more than 1500 group users
  111. # https://metacpan.org/dist/perl-ldap/view/lib/Net/LDAP/FAQ.pod#How-do-I-search-for-all-members-of-a-large-group-in-AD
  112. return group_user_memberof(entry) if entry.to_h.keys.any? { |key| key.to_s.include?('member;range') }
  113. return group_user_dns_memberuid(entry) if entry[:memberuid].present?
  114. entry[:uniquemember].presence
  115. end
  116. def group_user_dns_memberuid(entry)
  117. entry[:memberuid].filter_map do |uid|
  118. dn = nil
  119. @ldap.search("(&(uid=#{uid})#{@user_filter})", attributes: %w[dn]) do |user|
  120. dn = user.dn
  121. end
  122. dn
  123. end
  124. end
  125. def group_user_memberof(entry)
  126. result = []
  127. @ldap.search("(&(memberOf=#{entry.dn})#{@user_filter})", attributes: %w[dn]) do |user|
  128. result << user.dn
  129. end
  130. result
  131. end
  132. end
  133. end