user.rb 6.0 KB

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