Welcome to mirror list, hosted at ThFree Co, Russian Federation.

passwords_controller_spec.rb « controllers « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: cff84da7382be4dfe328320f1d487b23b01543b7 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe PasswordsController, feature_category: :system_access do
  include DeviseHelpers

  before do
    set_devise_mapping(context: @request)
  end

  describe '#check_password_authentication_available' do
    context 'when password authentication is disabled for the web interface and Git' do
      it 'prevents a password reset' do
        stub_application_setting(password_authentication_enabled_for_web: false)
        stub_application_setting(password_authentication_enabled_for_git: false)

        post :create

        expect(response).to have_gitlab_http_status(:found)
        expect(flash[:alert]).to eq _('Password authentication is unavailable.')
      end
    end

    context 'when reset email belongs to an ldap user' do
      let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') }

      it 'prevents a password reset' do
        post :create, params: { user: { email: user.email } }

        expect(flash[:alert]).to eq _('Password authentication is unavailable.')
      end
    end
  end

  describe '#update' do
    render_views

    context 'updating the password' do
      subject do
        put :update, params: {
          user: {
            password: password,
            password_confirmation: password_confirmation,
            reset_password_token: reset_password_token
          }
        }
      end

      let(:password) { User.random_password }
      let(:password_confirmation) { password }
      let(:reset_password_token) { user.send_reset_password_instructions }
      let(:user) { create(:user, password_automatically_set: true, password_expires_at: 10.minutes.ago) }

      context 'password update is successful' do
        it 'updates the password-related flags' do
          subject
          user.reload

          expect(response).to redirect_to(new_user_session_path)
          expect(flash[:notice]).to include('password has been changed successfully')
          expect(user.password_automatically_set).to eq(false)
          expect(user.password_expires_at).to be_nil
        end
      end

      context 'password update is unsuccessful' do
        let(:password_confirmation) { 'not_the_same_as_password' }

        it 'does not update the password-related flags' do
          subject
          user.reload

          expect(response).to render_template(:edit)
          expect(response.body).to have_content("Password confirmation doesn't match Password")
          expect(user.password_automatically_set).to eq(true)
          expect(user.password_expires_at).not_to be_nil
        end
      end

      context 'password is weak' do
        let(:password) { "password" }

        it 'tracks the event' do
          subject

          expect(response.body).to have_content("must not contain commonly used combinations of words and letters")
          expect_snowplow_event(
            category: 'Gitlab::Tracking::Helpers::WeakPasswordErrorEvent',
            action: 'track_weak_password_error',
            controller: 'PasswordsController',
            method: 'create'
          )
        end
      end

      it 'sets the username and caller_id in the context' do
        expect(controller).to receive(:update).and_wrap_original do |m, *args|
          m.call(*args)

          expect(Gitlab::ApplicationContext.current)
            .to include('meta.user' => user.username, 'meta.caller_id' => 'PasswordsController#update')
        end

        subject
      end
    end
  end

  describe '#create' do
    let(:user) { create(:user) }
    let(:email) { user.email }

    subject(:perform_request) { post(:create, params: { user: { email: email } }) }

    context 'when reCAPTCHA is disabled' do
      before do
        stub_application_setting(recaptcha_enabled: false)
      end

      it 'successfully sends password reset when reCAPTCHA is not solved' do
        perform_request

        expect(response).to redirect_to(new_user_session_path)
        expect(flash[:notice]).to include 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.'
      end
    end

    context 'when reCAPTCHA is enabled' do
      before do
        stub_application_setting(recaptcha_enabled: true)
      end

      context 'when the reCAPTCHA is not solved' do
        before do
          Recaptcha.configuration.skip_verify_env.delete('test')
        end

        it 'displays an error' do
          perform_request

          expect(response).to render_template(:new)
          expect(flash[:alert]).to include _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
        end

        it 'sets gon variables' do
          Gon.clear

          perform_request

          expect(response).to render_template(:new)
          expect(Gon.all_variables).not_to be_empty
        end
      end

      it 'successfully sends password reset when reCAPTCHA is solved' do
        Recaptcha.configuration.skip_verify_env << 'test'

        perform_request

        expect(response).to redirect_to(new_user_session_path)
        expect(flash[:notice]).to include 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.'
      end
    end

    context "sending 'Reset password instructions' email" do
      include EmailHelpers

      let_it_be(:user) { create(:user) }
      let_it_be(:user_confirmed_primary_email) { user.email }
      let_it_be(:user_confirmed_secondary_email) { create(:email, :confirmed, user: user, email: 'confirmed-secondary-email@example.com').email }
      let_it_be(:user_unconfirmed_secondary_email) { create(:email, user: user, email: 'unconfirmed-secondary-email@example.com').email }
      let_it_be(:unknown_email) { 'attacker@example.com' }
      let_it_be(:invalid_email) { 'invalid_email' }
      let_it_be(:sql_injection_email) { 'sql-injection-email@example.com OR 1=1' }
      let_it_be(:another_user_confirmed_primary_email) { create(:user).email }
      let_it_be(:another_user_unconfirmed_primary_email) { create(:user, :unconfirmed).email }

      before do
        reset_delivered_emails!

        perform_request

        perform_enqueued_jobs
      end

      context "when email param matches user's confirmed primary email" do
        let(:email) { user_confirmed_primary_email }

        it 'sends email to the primary email only' do
          expect_only_one_email_to_be_sent(subject: 'Reset password instructions', to: [user_confirmed_primary_email])
        end
      end

      context "when email param matches user's unconfirmed primary email" do
        let(:email) { another_user_unconfirmed_primary_email }

        # By default 'devise' gem allows password reset by unconfirmed primary email.
        # When user account with unconfirmed primary email that means it is unconfirmed.
        #
        # Password reset by unconfirmed primary email is very helpful from
        # security perspective. Example:
        # Malicious person creates user account on GitLab with someone's email.
        # If the email owner confirms the email for newly created account, the malicious person will be able
        # to sign in into the account by password they provided during account signup.
        # The malicious person could set up 2FA to the user account, after that
        # te email owner would not able to get access to that user account even
        # after performing password reset.
        # To deal with that case safely the email owner should reset password
        # for the user account first. That will make sure that after the user account
        # is confirmed the malicious person is not be able to sign in with
        # the password they provided during the account signup. Then email owner
        # could sign into the account, they will see a prompt to confirm the account email
        # to proceed. They can safely confirm the email and take over the account.
        # That is one of the reasons why password reset by unconfirmed primary email should be allowed.
        it 'sends email to the primary email only' do
          expect_only_one_email_to_be_sent(subject: 'Reset password instructions', to: [another_user_unconfirmed_primary_email])
        end
      end

      context "when email param matches user's confirmed secondary email" do
        let(:email) { user_confirmed_secondary_email }

        it 'sends email to the confirmed secondary email only' do
          expect_only_one_email_to_be_sent(subject: 'Reset password instructions', to: [user_confirmed_secondary_email])
        end
      end

      # While unconfirmed primary emails are linked with users accounts,
      # unconfirmed secondary emails should not be linked with any users till they are confirmed
      # See https://gitlab.com/gitlab-org/gitlab/-/issues/356665
      #
      # In https://gitlab.com/gitlab-org/gitlab/-/issues/367823, it is considerd
      # to prevent reserving emails on Gitlab by unconfirmed secondary emails.
      # As per this issue, there might be cases that there are multiple users
      # with the same unconfirmed secondary emails. It would be impossible to identify for
      # what user account password reset is requested if password reset were allowed
      # by unconfirmed secondary emails.
      # Also note that it is not possible to request email confirmation for
      # unconfirmed secondary emails without having access to the user account.
      context "when email param matches user's unconfirmed secondary email" do
        let(:email) { user_unconfirmed_secondary_email }

        it 'does not send email to anyone' do
          should_not_email_anyone
        end
      end

      context 'when email param is unknown email' do
        let(:email) { unknown_email }

        it 'does not send email to anyone' do
          should_not_email_anyone
        end
      end

      context 'when email param is invalid email' do
        let(:email) { invalid_email }

        it 'does not send email to anyone' do
          should_not_email_anyone
        end
      end

      context 'when email param with attempt to cause SQL injection' do
        let(:email) { sql_injection_email }

        it 'does not send email to anyone' do
          should_not_email_anyone
        end
      end

      # See https://gitlab.com/gitlab-org/gitlab/-/issues/436084
      context 'when email param with multiple emails' do
        let(:email) do
          [
            user_confirmed_primary_email,
            user_confirmed_secondary_email,
            user_unconfirmed_secondary_email,
            unknown_email,
            another_user_confirmed_primary_email,
            another_user_unconfirmed_primary_email
          ]
        end

        it 'does not send email to anyone' do
          should_not_email_anyone
        end
      end
    end
  end
end