user.rb 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Ldap
  3. # Class for handling LDAP Groups.
  4. class User
  5. include Ldap::FilterLookup
  6. IGNORED_ATTRIBUTES = %i[
  7. admincount
  8. accountexpires
  9. badpasswordtime
  10. badpwdcount
  11. countrycode
  12. distinguishedname
  13. dnshostname
  14. dscorepropagationdata
  15. instancetype
  16. iscriticalsystemobject
  17. useraccountcontrol
  18. usercertificate
  19. objectclass
  20. objectcategory
  21. objectsid
  22. primarygroupid
  23. pwdlastset
  24. lastlogoff
  25. lastlogon
  26. lastlogontimestamp
  27. localpolicyflags
  28. lockouttime
  29. logoncount
  30. logonhours
  31. msdfsr-computerreferencebl
  32. msds-supportedencryptiontypes
  33. ridsetreferences
  34. samaccounttype
  35. memberof
  36. serverreferencebl
  37. serviceprincipalname
  38. showinadvancedviewonly
  39. usnchanged
  40. usncreated
  41. whenchanged
  42. whencreated
  43. ].freeze
  44. # Returns the uid attribute.
  45. #
  46. # @param attributes [Hash{Symbol=>Array<String>}] A list of LDAP User attributes which should get checked for available uids.
  47. #
  48. # @example
  49. # Ldap::User.uid_attribute(attributes)
  50. #
  51. # @return [String] The uid attribute.
  52. def self.uid_attribute(attributes)
  53. result = nil
  54. %i[objectguid entryuuid samaccountname userprincipalname uid dn].each do |attribute|
  55. next if attributes[attribute].blank?
  56. result = attribute.to_s
  57. break
  58. end
  59. result
  60. end
  61. # Initializes a wrapper around Net::LDAP and ::Ldap to handle LDAP users.
  62. #
  63. # @param [Hash] config the configuration for establishing a LDAP connection. Default is Setting 'ldap_config'.
  64. # @option config [String] :uid_attribute The uid attribute. Default is determined automatically.
  65. # @option config [String] :filter The filter for LDAP users. Default is determined automatically.
  66. # @param ldap [Ldap] An optional existing Ldap class instance. Default is a new connection with given configuration.
  67. #
  68. # @example
  69. # ldap_user = Ldap::User.new
  70. #
  71. # @return [nil]
  72. def initialize(config, ldap: nil)
  73. @config = config
  74. @ldap = ldap || ::Ldap.new(@config)
  75. handle_config
  76. end
  77. # Checks if given username and password combination is valid for the connected LDAP.
  78. #
  79. # @param username [String] The username.
  80. # @param password [String] The password.
  81. #
  82. # @example
  83. # ldap_user.valid?('example_user', 'pw1234')
  84. # #=> true
  85. #
  86. # @return [Boolean] The valid state of the username and password combination.
  87. def valid?(username, password)
  88. bind_success = @ldap.connection.bind_as(
  89. base: @ldap.base_dn,
  90. filter: @user_filter ? "(&(#{login_attribute}=#{username})#{@user_filter})" : "(#{login_attribute}=#{username})",
  91. password: password
  92. )
  93. message = bind_success ? 'successful' : 'failed'
  94. Rails.logger.info "ldap authentication for user '#{username}' (#{login_attribute}) #{message}!"
  95. bind_success.present?
  96. end
  97. # Determines possible User attributes with example values.
  98. #
  99. # @param filter [String] The filter for listing users. Default is initialization parameter.
  100. # @param base_dn [String] The applied base DN for listing users. Default is Ldap#base_dn.
  101. #
  102. # @example
  103. # ldap_user.attributes
  104. # #=> {:dn=>"dn (e. g. CN=Administrator,CN=Users,DC=domain,DC=tld)", ...}
  105. #
  106. # @return [Hash{Symbol=>String}] The available User attributes as key and the name and an example as value.
  107. def attributes(custom_filter: nil, base_dn: nil)
  108. @attributes ||= begin
  109. attributes = {}.with_indifferent_access
  110. lookup_counter = 0
  111. # collect sample attributes
  112. @ldap.search(custom_filter || filter, base: base_dn) do |entry|
  113. pre_merge_count = attributes.count
  114. attributes.reverse_merge!(entry.to_h
  115. .except(*IGNORED_ATTRIBUTES)
  116. .transform_values(&:first)
  117. .compact)
  118. # check max 50 entries with the same attributes in a row
  119. lookup_counter = (pre_merge_count < attributes.count ? 0 : lookup_counter.next)
  120. break if lookup_counter >= 50
  121. end
  122. # format sample values for presentation
  123. attributes.each do |name, value|
  124. attributes[name] = if value.encoding == Encoding.find('ascii-8bit')
  125. "#{name} (binary data)"
  126. else
  127. "#{name} (e.g., #{value.utf8_encode})"
  128. end
  129. end
  130. end
  131. end
  132. # The active filter of the instance. If none give on initialization an automatic lookup is performed.
  133. #
  134. # @example
  135. # ldap_user.filter
  136. # #=> '(objectClass=user)'
  137. #
  138. # @return [String, nil] The active or found filter or nil if none could be found.
  139. def filter
  140. @filter ||= lookup_filter(['(&(objectClass=user)(samaccountname=*)(!(samaccountname=*$)))', '(objectClass=user)', '(objectClass=posixaccount)', '(objectClass=person)'])
  141. end
  142. # The active uid attribute of the instance. If none give on initialization an automatic lookup is performed.
  143. #
  144. # @example
  145. # ldap_user.uid_attribute
  146. # #=> 'samaccountname'
  147. #
  148. # @return [String, nil] The active or found uid attribute or nil if none could be found.
  149. def uid_attribute
  150. @uid_attribute ||= self.class.uid_attribute(attributes)
  151. end
  152. private
  153. attr_reader :config
  154. def login_attribute
  155. @login_attribute ||= config[:user_attributes]&.key('login') || uid_attribute
  156. end
  157. def handle_config
  158. return if config.blank?
  159. @uid_attribute = config[:uid_attribute]
  160. @filter = config[:filter]
  161. @user_filter = config[:user_filter]
  162. end
  163. end
  164. end