person.rb 4.13 KB
Newer Older
1 2
# Contains methods common to both GitLab CE and EE.
# All EE methods should be in `EE::Gitlab::LDAP::Person` only.
3 4 5
module Gitlab
  module LDAP
    class Person
6
      prepend ::EE::Gitlab::LDAP::Person
7

8 9 10
      # 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/
11
      AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
12

13 14
      InvalidEntryError = Class.new(StandardError)

15 16 17
      attr_accessor :entry, :provider

      def self.find_by_uid(uid, adapter)
18
        uid = Net::LDAP::Filter.escape(uid)
19
        adapter.user(adapter.config.uid, uid)
20 21
      end

22
      def self.find_by_dn(dn, adapter)
23
        adapter.user('dn', dn)
24 25
      end

26
      def self.find_by_email(email, adapter)
27 28 29
        email_fields = adapter.config.attributes['email']

        adapter.user(email_fields, email)
30 31
      end

32
      def self.disabled_via_active_directory?(dn, adapter)
33 34 35
        adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
      end

36 37
      def self.ldap_attributes(config)
        [
38 39 40 41 42 43
          'dn',
          config.uid,
          *config.attributes['name'],
          *config.attributes['email'],
          *config.attributes['username']
        ].compact.uniq
44 45
      end

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
      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

      # 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)
        ::Gitlab::LDAP::DN.normalize_value(uid)
      rescue ::Gitlab::LDAP::DN::FormatError => e
        Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")

        uid
      end

66
      def initialize(entry, provider)
67
        Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
68
        @entry = entry
69
        @provider = provider
70 71

        validate_entry
72 73 74
      end

      def name
75
        attribute_value(:name).first
76 77
      end

78
      def uid
79
        entry.public_send(config.uid).first # rubocop:disable GitlabSecurity/PublicSend
80 81
      end

82
      def username
83 84 85 86 87 88
        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.
Francisco Javier López's avatar
Francisco Javier López committed
89 90 91
        [username].flatten.first.tap do |username|
          username.downcase! if config.lowercase_usernames
        end
92 93
      end

94
      def email
95
        attribute_value(:email)
96 97
      end

98 99 100
      def dn
        self.class.normalize_dn(entry.dn)
      end
101

102 103
      private

104 105 106 107
      def entry
        @entry
      end

108
      def config
109
        @config ||= Gitlab::LDAP::Config.new(provider)
110
      end
111 112 113 114 115 116

      # 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)
117
        attributes = Array(config.attributes[attribute.to_s])
118 119 120 121
        selected_attr = attributes.find { |attr| entry.respond_to?(attr) }

        return nil unless selected_attr

122
        entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
123
      end
124 125 126 127 128 129 130 131 132 133 134 135 136

      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
137 138 139
    end
  end
end