summaryrefslogtreecommitdiff
path: root/lib/gitlab/o_auth/user.rb
blob: a8b4dc2a83fe9d179db747eae3397061578398e9 (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# OAuth extension for User model
#
# * Find GitLab user based on omniauth uid and provider
# * Create new user from omniauth data
#
module Gitlab
  module OAuth
    class SignupDisabledError < StandardError; end

    class User
      attr_accessor :auth_hash, :gl_user

      def initialize(auth_hash)
        self.auth_hash = auth_hash
      end

      def persisted?
        gl_user.try(:persisted?)
      end

      def new?
        !persisted?
      end

      def valid?
        gl_user.try(:valid?)
      end

      def save(provider = 'OAuth')
        unauthorized_to_create unless gl_user

        if needs_blocking?
          gl_user.save!
          gl_user.block
        else
          gl_user.save!
        end

        log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
        gl_user
      rescue ActiveRecord::RecordInvalid => e
        log.info "(#{provider}) Error saving user: #{gl_user.errors.full_messages}"
        return self, e.record.errors
      end

      def gl_user
        @user ||= find_by_uid_and_provider

        if auto_link_ldap_user?
          @user ||= find_or_create_ldap_user
        end

        if signup_enabled?
          @user ||= build_new_user
        end

        if external_provider? && @user
          @user.external = true
        end

        @user
      end

      protected

      def find_or_create_ldap_user
        return unless ldap_person

        # If a corresponding person exists with same uid in a LDAP server,
        # check if the user already has a GitLab account.
        user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
        if user
          # Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account.
          log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
          user.identities.find_or_initialize_by(extern_uid: auth_hash.uid, provider: auth_hash.provider)
        else
          log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account."
          user = find_by_uid_and_provider
          if user.nil?
            log.info "No user found using #{auth_hash.provider} provider. Creating a new one."
            user = build_new_user
          end
          log.info "Correct account has been found. Adding LDAP identity to user: #{user.username}."
          user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
        end

        user
      end

      def auto_link_ldap_user?
        Gitlab.config.omniauth.auto_link_ldap_user
      end

      def creating_linked_ldap_user?
        auto_link_ldap_user? && ldap_person
      end

      def ldap_person
        return @ldap_person if defined?(@ldap_person)

        # Look for a corresponding person with same uid in any of the configured LDAP providers
        Gitlab::LDAP::Config.providers.each do |provider|
          adapter = Gitlab::LDAP::Adapter.new(provider)
          @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
          # The `uid` might actually be a DN. Try it next.
          @ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
          break if @ldap_person
        end
        @ldap_person
      end

      def ldap_config
        Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
      end

      def needs_blocking?
        new? && block_after_signup?
      end

      def signup_enabled?
        providers = Gitlab.config.omniauth.allow_single_sign_on
        if providers.is_a?(Array)
          providers.include?(auth_hash.provider)
        else
          providers
        end
      end

      def external_provider?
        Gitlab.config.omniauth.external_providers.include?(auth_hash.provider)
      end

      def block_after_signup?
        if creating_linked_ldap_user?
          ldap_config.block_auto_created_users
        else
          Gitlab.config.omniauth.block_auto_created_users
        end
      end

      def auth_hash=(auth_hash)
        @auth_hash = AuthHash.new(auth_hash)
      end

      def find_by_uid_and_provider
        identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
        identity && identity.user
      end

      def build_new_user
        user = ::User.new(user_attributes)
        user.skip_confirmation!
        user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
        user
      end

      def user_attributes
        # Give preference to LDAP for sensitive information when creating a linked account
        if creating_linked_ldap_user?
          username = ldap_person.username.presence
          email = ldap_person.email.first.presence
        end

        username ||= auth_hash.username
        email ||= auth_hash.email

        name = auth_hash.name
        name = ::Namespace.clean_path(username) if name.strip.empty?

        {
          name:                       name,
          username:                   ::Namespace.clean_path(username),
          email:                      email,
          password:                   auth_hash.password,
          password_confirmation:      auth_hash.password,
          password_automatically_set: true
        }
      end

      def log
        Gitlab::AppLogger
      end

      def unauthorized_to_create
        raise SignupDisabledError
      end
    end
  end
end