2014-03-10 12:48:08 +00:00
|
|
|
module Gitlab
|
|
|
|
module LDAP
|
|
|
|
class Person
|
2014-05-14 17:08:42 +00:00
|
|
|
# Active Directory-specific LDAP filter that checks if bit 2 of the
|
|
|
|
# userAccountControl attribute is set.
|
|
|
|
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
|
2014-05-14 16:54:05 +00:00
|
|
|
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
|
2014-05-14 16:26:58 +00:00
|
|
|
|
2017-11-08 21:32:12 +00:00
|
|
|
InvalidEntryError = Class.new(StandardError)
|
|
|
|
|
2014-10-13 15:24:05 +00:00
|
|
|
attr_accessor :entry, :provider
|
|
|
|
|
|
|
|
def self.find_by_uid(uid, adapter)
|
2015-03-06 12:26:33 +00:00
|
|
|
uid = Net::LDAP::Filter.escape(uid)
|
2014-10-14 08:54:43 +00:00
|
|
|
adapter.user(adapter.config.uid, uid)
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
2014-10-13 15:24:05 +00:00
|
|
|
def self.find_by_dn(dn, adapter)
|
2014-03-14 07:52:57 +00:00
|
|
|
adapter.user('dn', dn)
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
2017-09-12 09:50:28 +00:00
|
|
|
def self.find_by_email(email, adapter)
|
2017-09-25 17:07:45 +00:00
|
|
|
email_fields = adapter.config.attributes['email']
|
|
|
|
|
|
|
|
adapter.user(email_fields, email)
|
2017-09-12 09:50:28 +00:00
|
|
|
end
|
|
|
|
|
2014-10-13 15:24:05 +00:00
|
|
|
def self.disabled_via_active_directory?(dn, adapter)
|
2014-05-14 16:26:58 +00:00
|
|
|
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
|
|
|
|
end
|
|
|
|
|
2017-08-23 17:18:13 +00:00
|
|
|
def self.ldap_attributes(config)
|
|
|
|
[
|
2017-11-08 21:32:12 +00:00
|
|
|
'dn',
|
|
|
|
config.uid,
|
|
|
|
*config.attributes['name'],
|
|
|
|
*config.attributes['email'],
|
|
|
|
*config.attributes['username']
|
|
|
|
].compact.uniq
|
2017-08-23 17:18:13 +00:00
|
|
|
end
|
|
|
|
|
2017-10-05 10:47:48 +00:00
|
|
|
def self.normalize_dn(dn)
|
|
|
|
::Gitlab::LDAP::DN.new(dn).to_normalized_s
|
|
|
|
rescue ::Gitlab::LDAP::DN::FormatError => e
|
|
|
|
Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
|
|
|
|
|
|
|
|
dn
|
|
|
|
end
|
|
|
|
|
2017-09-18 04:28:54 +00:00
|
|
|
# Returns the UID in a normalized form.
|
|
|
|
#
|
|
|
|
# 1. Excess spaces are stripped
|
|
|
|
# 2. The string is downcased (for case-insensitivity)
|
|
|
|
def self.normalize_uid(uid)
|
2017-10-03 21:38:55 +00:00
|
|
|
::Gitlab::LDAP::DN.normalize_value(uid)
|
2017-10-05 10:27:07 +00:00
|
|
|
rescue ::Gitlab::LDAP::DN::FormatError => e
|
2017-09-18 23:40:04 +00:00
|
|
|
Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")
|
|
|
|
|
|
|
|
uid
|
2017-09-18 04:28:54 +00:00
|
|
|
end
|
|
|
|
|
2014-10-13 15:24:05 +00:00
|
|
|
def initialize(entry, provider)
|
2014-03-10 12:48:08 +00:00
|
|
|
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
|
|
|
|
@entry = entry
|
2014-10-13 15:24:05 +00:00
|
|
|
@provider = provider
|
2017-11-08 21:32:12 +00:00
|
|
|
|
|
|
|
validate_entry
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def name
|
2017-01-05 22:01:04 +00:00
|
|
|
attribute_value(:name).first
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def uid
|
2017-08-10 16:39:26 +00:00
|
|
|
entry.public_send(config.uid).first # rubocop:disable GitlabSecurity/PublicSend
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def username
|
2017-11-08 21:32:12 +00:00
|
|
|
username = attribute_value(:username)
|
|
|
|
|
|
|
|
# Depending on the attribute, multiple values may
|
|
|
|
# be returned. We need only one for username.
|
|
|
|
# Ex. `uid` returns only one value but `mail` may
|
|
|
|
# return an array of multiple email addresses.
|
2018-02-05 22:35:34 +00:00
|
|
|
[username].flatten.first.tap do |username|
|
|
|
|
username.downcase! if config.lowercase_usernames
|
|
|
|
end
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
|
2014-10-13 15:24:05 +00:00
|
|
|
def email
|
2017-01-03 16:05:47 +00:00
|
|
|
attribute_value(:email)
|
2014-10-13 15:24:05 +00:00
|
|
|
end
|
|
|
|
|
2017-09-18 01:07:29 +00:00
|
|
|
def dn
|
2017-10-05 10:47:48 +00:00
|
|
|
self.class.normalize_dn(entry.dn)
|
2017-09-18 01:07:29 +00:00
|
|
|
end
|
2014-03-10 12:48:08 +00:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def entry
|
|
|
|
@entry
|
|
|
|
end
|
|
|
|
|
|
|
|
def config
|
2014-10-13 15:24:05 +00:00
|
|
|
@config ||= Gitlab::LDAP::Config.new(provider)
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
2017-01-03 16:05:47 +00:00
|
|
|
|
|
|
|
# Using the LDAP attributes configuration, find and return the first
|
|
|
|
# attribute with a value. For example, by default, when given 'email',
|
|
|
|
# this method looks for 'mail', 'email' and 'userPrincipalName' and
|
|
|
|
# returns the first with a value.
|
|
|
|
def attribute_value(attribute)
|
2017-01-05 22:01:04 +00:00
|
|
|
attributes = Array(config.attributes[attribute.to_s])
|
2017-01-03 16:05:47 +00:00
|
|
|
selected_attr = attributes.find { |attr| entry.respond_to?(attr) }
|
|
|
|
|
|
|
|
return nil unless selected_attr
|
|
|
|
|
2017-08-10 16:39:26 +00:00
|
|
|
entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
|
2017-01-03 16:05:47 +00:00
|
|
|
end
|
2017-11-08 21:32:12 +00:00
|
|
|
|
|
|
|
def validate_entry
|
|
|
|
allowed_attrs = self.class.ldap_attributes(config).map(&:downcase)
|
|
|
|
|
|
|
|
# Net::LDAP::Entry transforms keys to symbols. Change to strings to compare.
|
|
|
|
entry_attrs = entry.attribute_names.map { |n| n.to_s.downcase }
|
|
|
|
invalid_attrs = entry_attrs - allowed_attrs
|
|
|
|
|
|
|
|
if invalid_attrs.any?
|
|
|
|
raise InvalidEntryError,
|
|
|
|
"#{self.class.name} initialized with Net::LDAP::Entry containing invalid attributes(s): #{invalid_attrs}"
|
|
|
|
end
|
|
|
|
end
|
2014-03-10 12:48:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|