summaryrefslogtreecommitdiff
path: root/lib/gitlab/auth/ldap/person.rb
blob: 8dfae3ee5417206015dffeefd6a30a0e9f8c8d61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
module Gitlab
  module Auth
    module LDAP
      class Person
        # 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/
        AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")

        InvalidEntryError = Class.new(StandardError)

        attr_accessor :entry, :provider

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

        def self.find_by_dn(dn, adapter)
          adapter.user('dn', dn)
        end

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

          adapter.user(email_fields, email)
        end

        def self.disabled_via_active_directory?(dn, adapter)
          adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
        end

        def self.ldap_attributes(config)
          [
            'dn',
            config.uid,
            *config.attributes['name'],
            *config.attributes['email'],
            *config.attributes['username']
          ].compact.uniq
        end

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

          uid
        end

        def initialize(entry, provider)
          Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
          @entry = entry
          @provider = provider
        end

        def name
          attribute_value(:name).first
        end

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

        def username
          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.
          [username].flatten.first.tap do |username|
            username.downcase! if config.lowercase_usernames
          end
        end

        def email
          attribute_value(:email)
        end

        def dn
          self.class.normalize_dn(entry.dn)
        end

        private

        def entry
          @entry
        end

        def config
          @config ||= Gitlab::Auth::LDAP::Config.new(provider)
        end

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

          return nil unless selected_attr

          entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
        end
      end
    end
  end
end