PageRenderTime 178ms CodeModel.GetById 2ms app.highlight 168ms RepoModel.GetById 2ms app.codeStats 0ms

/spec/controllers/session_controller_spec.rb

https://bitbucket.org/bitworkvn/cs_discourse
Ruby | 985 lines | 823 code | 154 blank | 8 comment | 9 complexity | 27b9e19a320746834853c1553060a394 MD5 | raw file
  1require 'rails_helper'
  2
  3describe SessionController do
  4  shared_examples 'failed to continue local login' do
  5    it 'should return the right response' do
  6      expect(response).not_to be_success
  7      expect(response.status.to_i).to eq 500
  8    end
  9  end
 10
 11  describe '#become' do
 12    let!(:user) { Fabricate(:user) }
 13
 14    it "does not work when in production mode" do
 15      Rails.env.stubs(:production?).returns(true)
 16      get :become, params: { session_id: user.username }, format: :json
 17
 18      expect(response.status).to eq(403)
 19      expect(JSON.parse(response.body)["error_type"]).to eq("invalid_access")
 20      expect(session[:current_user_id]).to be_blank
 21    end
 22
 23    it "works in developmenet mode" do
 24      Rails.env.stubs(:development?).returns(true)
 25      get :become, params: { session_id: user.username }, format: :json
 26      expect(response).to be_redirect
 27      expect(session[:current_user_id]).to eq(user.id)
 28    end
 29  end
 30
 31  describe '#sso_login' do
 32
 33    before do
 34      @sso_url = "http://somesite.com/discourse_sso"
 35      @sso_secret = "shjkfdhsfkjh"
 36
 37      request.host = Discourse.current_hostname
 38
 39      SiteSetting.sso_url = @sso_url
 40      SiteSetting.enable_sso = true
 41      SiteSetting.sso_secret = @sso_secret
 42
 43      # We have 2 options, either fabricate an admin or don't
 44      # send welcome messages
 45      Fabricate(:admin)
 46      # skip for now
 47      # SiteSetting.send_welcome_message = false
 48    end
 49
 50    def get_sso(return_path)
 51      nonce = SecureRandom.hex
 52      dso = DiscourseSingleSignOn.new
 53      dso.nonce = nonce
 54      dso.register_nonce(return_path)
 55
 56      sso = SingleSignOn.new
 57      sso.nonce = nonce
 58      sso.sso_secret = @sso_secret
 59      sso
 60    end
 61
 62    it 'can take over an account' do
 63      sso = get_sso("/")
 64      user = Fabricate(:user)
 65      sso.email = user.email
 66      sso.external_id = 'abc'
 67      sso.username = 'sam'
 68
 69      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
 70
 71      expect(response).to redirect_to('/')
 72      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
 73      expect(logged_on_user.email).to eq(user.email)
 74      expect(logged_on_user.single_sign_on_record.external_id).to eq("abc")
 75      expect(logged_on_user.single_sign_on_record.external_username).to eq('sam')
 76    end
 77
 78    def sso_for_ip_specs
 79      sso = get_sso('/a/')
 80      sso.external_id = '666' # the number of the beast
 81      sso.email = 'bob@bob.com'
 82      sso.name = 'Sam Saffron'
 83      sso.username = 'sam'
 84      sso
 85    end
 86
 87    it 'respects IP restrictions on create' do
 88      screened_ip = Fabricate(:screened_ip_address)
 89      ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(screened_ip.ip_address)
 90
 91      sso = sso_for_ip_specs
 92      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
 93
 94      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
 95      expect(logged_on_user).to eq(nil)
 96    end
 97
 98    it 'respects IP restrictions on login' do
 99      sso = sso_for_ip_specs
100      _user = DiscourseSingleSignOn.parse(sso.payload).lookup_or_create_user(request.remote_ip)
101
102      sso = sso_for_ip_specs
103      screened_ip = Fabricate(:screened_ip_address)
104      ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(screened_ip.ip_address)
105
106      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
107      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
108      expect(logged_on_user).to be_blank
109    end
110
111    it 'respects email restrictions' do
112      sso = get_sso('/a/')
113      sso.external_id = '666' # the number of the beast
114      sso.email = 'bob@bob.com'
115      sso.name = 'Sam Saffron'
116      sso.username = 'sam'
117
118      ScreenedEmail.block('bob@bob.com')
119      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
120
121      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
122      expect(logged_on_user).to eq(nil)
123    end
124
125    it 'allows you to create an admin account' do
126      sso = get_sso('/a/')
127      sso.external_id = '666' # the number of the beast
128      sso.email = 'bob@bob.com'
129      sso.name = 'Sam Saffron'
130      sso.username = 'sam'
131      sso.custom_fields["shop_url"] = "http://my_shop.com"
132      sso.custom_fields["shop_name"] = "Sam"
133      sso.admin = true
134
135      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
136
137      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
138      expect(logged_on_user.admin).to eq(true)
139    end
140
141    it 'redirects to a non-relative url' do
142      sso = get_sso("#{Discourse.base_url}/b/")
143      sso.external_id = '666' # the number of the beast
144      sso.email = 'bob@bob.com'
145      sso.name = 'Sam Saffron'
146      sso.username = 'sam'
147
148      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
149      expect(response).to redirect_to('/b/')
150    end
151
152    it 'redirects to random url if it is allowed' do
153      SiteSetting.sso_allows_all_return_paths = true
154
155      sso = get_sso('https://gusundtrout.com')
156      sso.external_id = '666' # the number of the beast
157      sso.email = 'bob@bob.com'
158      sso.name = 'Sam Saffron'
159      sso.username = 'sam'
160
161      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
162      expect(response).to redirect_to('https://gusundtrout.com')
163    end
164
165    it 'redirects to root if the host of the return_path is different' do
166      sso = get_sso('//eviltrout.com')
167      sso.external_id = '666' # the number of the beast
168      sso.email = 'bob@bob.com'
169      sso.name = 'Sam Saffron'
170      sso.username = 'sam'
171
172      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
173      expect(response).to redirect_to('/')
174    end
175
176    it 'redirects to root if the host of the return_path is different' do
177      sso = get_sso('http://eviltrout.com')
178      sso.external_id = '666' # the number of the beast
179      sso.email = 'bob@bob.com'
180      sso.name = 'Sam Saffron'
181      sso.username = 'sam'
182
183      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
184      expect(response).to redirect_to('/')
185    end
186
187    it 'allows you to create an account' do
188      sso = get_sso('/a/')
189      sso.external_id = '666' # the number of the beast
190      sso.email = 'bob@bob.com'
191      sso.name = 'Sam Saffron'
192      sso.username = 'sam'
193      sso.custom_fields["shop_url"] = "http://my_shop.com"
194      sso.custom_fields["shop_name"] = "Sam"
195
196      events = DiscourseEvent.track_events do
197        get :sso_login, params: Rack::Utils.parse_query(sso.payload)
198      end
199
200      expect(events.map { |event| event[:event_name] }).to include(
201       :user_logged_in, :user_first_logged_in
202      )
203
204      expect(response).to redirect_to('/a/')
205
206      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
207
208      # ensure nothing is transient
209      logged_on_user = User.find(logged_on_user.id)
210
211      expect(logged_on_user.admin).to eq(false)
212      expect(logged_on_user.email).to eq('bob@bob.com')
213      expect(logged_on_user.name).to eq('Sam Saffron')
214      expect(logged_on_user.username).to eq('sam')
215
216      expect(logged_on_user.single_sign_on_record.external_id).to eq("666")
217      expect(logged_on_user.single_sign_on_record.external_username).to eq('sam')
218      expect(logged_on_user.active).to eq(true)
219      expect(logged_on_user.custom_fields["shop_url"]).to eq("http://my_shop.com")
220      expect(logged_on_user.custom_fields["shop_name"]).to eq("Sam")
221      expect(logged_on_user.custom_fields["bla"]).to eq(nil)
222    end
223
224    context 'when sso emails are not trusted' do
225      context 'if you have not activated your account' do
226        it 'does not log you in' do
227          sso = get_sso('/a/')
228          sso.external_id = '666' # the number of the beast
229          sso.email = 'bob@bob.com'
230          sso.name = 'Sam Saffron'
231          sso.username = 'sam'
232          sso.require_activation = true
233
234          get :sso_login, params: Rack::Utils.parse_query(sso.payload)
235
236          logged_on_user = Discourse.current_user_provider.new(request.env).current_user
237          expect(logged_on_user).to eq(nil)
238        end
239
240        it 'sends an activation email' do
241          Jobs.expects(:enqueue).with(:critical_user_email, has_entries(type: :signup))
242          sso = get_sso('/a/')
243          sso.external_id = '666' # the number of the beast
244          sso.email = 'bob@bob.com'
245          sso.name = 'Sam Saffron'
246          sso.username = 'sam'
247          sso.require_activation = true
248
249          get :sso_login, params: Rack::Utils.parse_query(sso.payload)
250        end
251      end
252
253      context 'if you have activated your account' do
254        it 'allows you to log in' do
255          sso = get_sso('/hello/world')
256          sso.external_id = '997'
257          sso.sso_url = "http://somewhere.over.com/sso_login"
258          sso.require_activation = true
259
260          user = Fabricate(:user)
261          user.create_single_sign_on_record(external_id: '997', last_payload: '')
262          user.stubs(:active?).returns(true)
263
264          get :sso_login, params: Rack::Utils.parse_query(sso.payload)
265
266          logged_on_user = Discourse.current_user_provider.new(request.env).current_user
267          expect(user.id).to eq(logged_on_user.id)
268        end
269      end
270    end
271
272    it 'allows login to existing account with valid nonce' do
273      sso = get_sso('/hello/world')
274      sso.external_id = '997'
275      sso.sso_url = "http://somewhere.over.com/sso_login"
276
277      user = Fabricate(:user)
278      user.create_single_sign_on_record(external_id: '997', last_payload: '')
279
280      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
281
282      user.single_sign_on_record.reload
283      expect(user.single_sign_on_record.last_payload).to eq(sso.unsigned_payload)
284
285      expect(response).to redirect_to('/hello/world')
286      logged_on_user = Discourse.current_user_provider.new(request.env).current_user
287
288      expect(user.id).to eq(logged_on_user.id)
289
290      # nonce is bad now
291      get :sso_login, params: Rack::Utils.parse_query(sso.payload)
292      expect(response.code).to eq('419')
293    end
294
295    describe 'can act as an SSO provider' do
296      before do
297        SiteSetting.enable_sso_provider = true
298        SiteSetting.enable_sso = false
299        SiteSetting.enable_local_logins = true
300        SiteSetting.sso_secret = "topsecret"
301
302        @sso = SingleSignOn.new
303        @sso.nonce = "mynonce"
304        @sso.sso_secret = SiteSetting.sso_secret
305        @sso.return_sso_url = "http://somewhere.over.rainbow/sso"
306
307        @user = Fabricate(:user, password: "myfrogs123ADMIN", active: true, admin: true)
308        group = Fabricate(:group)
309        group.add(@user)
310        @user.reload
311        EmailToken.update_all(confirmed: true)
312      end
313
314      it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do
315        get :sso_provider, params: Rack::Utils.parse_query(@sso.payload)
316        expect(response).to redirect_to("/login")
317
318        post :create,
319          params: { login: @user.username, password: "myfrogs123ADMIN" },
320          format: :json,
321          xhr: true
322
323        location = response.cookies["sso_destination_url"]
324        # javascript code will handle redirection of user to return_sso_url
325        expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
326
327        payload = location.split("?")[1]
328        sso2 = SingleSignOn.parse(payload, "topsecret")
329
330        expect(sso2.email).to eq(@user.email)
331        expect(sso2.name).to eq(@user.name)
332        expect(sso2.username).to eq(@user.username)
333        expect(sso2.external_id).to eq(@user.id.to_s)
334        expect(sso2.admin).to eq(true)
335        expect(sso2.moderator).to eq(false)
336        expect(sso2.groups).to eq(@user.groups.pluck(:name).join(","))
337      end
338
339      it "successfully redirects user to return_sso_url when the user is logged in" do
340        log_in_user(@user)
341
342        get :sso_provider, params: Rack::Utils.parse_query(@sso.payload)
343
344        location = response.header["Location"]
345        expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
346
347        payload = location.split("?")[1]
348        sso2 = SingleSignOn.parse(payload, "topsecret")
349
350        expect(sso2.email).to eq(@user.email)
351        expect(sso2.name).to eq(@user.name)
352        expect(sso2.username).to eq(@user.username)
353        expect(sso2.external_id).to eq(@user.id.to_s)
354        expect(sso2.admin).to eq(true)
355        expect(sso2.moderator).to eq(false)
356      end
357    end
358
359    describe 'local attribute override from SSO payload' do
360      before do
361        SiteSetting.email_editable = false
362        SiteSetting.sso_overrides_email = true
363        SiteSetting.sso_overrides_username = true
364        SiteSetting.sso_overrides_name = true
365
366        @user = Fabricate(:user)
367
368        @sso = get_sso('/hello/world')
369        @sso.external_id = '997'
370
371        @reversed_username = @user.username.reverse
372        @sso.username = @reversed_username
373        @sso.email = "#{@reversed_username}@garbage.org"
374        @reversed_name = @user.name.reverse
375        @sso.name = @reversed_name
376
377        @suggested_username = UserNameSuggester.suggest(@sso.username || @sso.name || @sso.email)
378        @suggested_name = User.suggest_name(@sso.name || @sso.username || @sso.email)
379        @user.create_single_sign_on_record(external_id: '997', last_payload: '')
380      end
381
382      it 'stores the external attributes' do
383        get :sso_login, params: Rack::Utils.parse_query(@sso.payload)
384        @user.single_sign_on_record.reload
385        expect(@user.single_sign_on_record.external_username).to eq(@sso.username)
386        expect(@user.single_sign_on_record.external_email).to eq(@sso.email)
387        expect(@user.single_sign_on_record.external_name).to eq(@sso.name)
388      end
389
390      it 'overrides attributes' do
391        get :sso_login, params: Rack::Utils.parse_query(@sso.payload)
392
393        logged_on_user = Discourse.current_user_provider.new(request.env).current_user
394        expect(logged_on_user.username).to eq(@suggested_username)
395        expect(logged_on_user.email).to eq("#{@reversed_username}@garbage.org")
396        expect(logged_on_user.name).to eq(@sso.name)
397      end
398
399      it 'does not change matching attributes for an existing account' do
400        @sso.username = @user.username
401        @sso.name = @user.name
402        @sso.email = @user.email
403
404        get :sso_login, params: Rack::Utils.parse_query(@sso.payload)
405
406        logged_on_user = Discourse.current_user_provider.new(request.env).current_user
407        expect(logged_on_user.username).to eq(@user.username)
408        expect(logged_on_user.name).to eq(@user.name)
409        expect(logged_on_user.email).to eq(@user.email)
410      end
411
412    end
413  end
414
415  describe '#sso_provider' do
416    before do
417      SiteSetting.enable_sso_provider = true
418      SiteSetting.enable_sso = false
419      SiteSetting.enable_local_logins = true
420      SiteSetting.sso_secret = "topsecret"
421
422      @sso = SingleSignOn.new
423      @sso.nonce = "mynonce"
424      @sso.sso_secret = SiteSetting.sso_secret
425      @sso.return_sso_url = "http://somewhere.over.rainbow/sso"
426
427      @user = Fabricate(:user, password: "myfrogs123ADMIN", active: true, admin: true)
428      EmailToken.update_all(confirmed: true)
429    end
430
431    it "successfully logs in and redirects user to return_sso_url when the user is not logged in" do
432      get :sso_provider, params: Rack::Utils.parse_query(@sso.payload)
433      expect(response).to redirect_to("/login")
434
435      post :create,
436        params: { login: @user.username, password: "myfrogs123ADMIN" },
437        format: :json,
438        xhr: true
439
440      location = response.cookies["sso_destination_url"]
441      # javascript code will handle redirection of user to return_sso_url
442      expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
443
444      payload = location.split("?")[1]
445      sso2 = SingleSignOn.parse(payload, "topsecret")
446
447      expect(sso2.email).to eq(@user.email)
448      expect(sso2.name).to eq(@user.name)
449      expect(sso2.username).to eq(@user.username)
450      expect(sso2.external_id).to eq(@user.id.to_s)
451      expect(sso2.admin).to eq(true)
452      expect(sso2.moderator).to eq(false)
453    end
454
455    it "successfully redirects user to return_sso_url when the user is logged in" do
456      log_in_user(@user)
457
458      get :sso_provider, params: Rack::Utils.parse_query(@sso.payload)
459
460      location = response.header["Location"]
461      expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
462
463      payload = location.split("?")[1]
464      sso2 = SingleSignOn.parse(payload, "topsecret")
465
466      expect(sso2.email).to eq(@user.email)
467      expect(sso2.name).to eq(@user.name)
468      expect(sso2.username).to eq(@user.username)
469      expect(sso2.external_id).to eq(@user.id.to_s)
470      expect(sso2.admin).to eq(true)
471      expect(sso2.moderator).to eq(false)
472    end
473  end
474
475  describe '#create' do
476
477    let(:user) { Fabricate(:user) }
478
479    context 'local login is disabled' do
480      before do
481        SiteSetting.enable_local_logins = false
482
483        post :create, params: {
484          login: user.username, password: 'myawesomepassword'
485        }, format: :json
486      end
487      it_behaves_like "failed to continue local login"
488    end
489
490    context 'SSO is enabled' do
491      before do
492        SiteSetting.sso_url = "https://www.example.com/sso"
493        SiteSetting.enable_sso = true
494
495        post :create, params: {
496          login: user.username, password: 'myawesomepassword'
497        }, format: :json
498      end
499      it_behaves_like "failed to continue local login"
500    end
501
502    context 'when email is confirmed' do
503      before do
504        token = user.email_tokens.find_by(email: user.email)
505        EmailToken.confirm(token.token)
506      end
507
508      it "raises an error when the login isn't present" do
509        expect do
510          post :create, format: :json
511        end.to raise_error(ActionController::ParameterMissing)
512      end
513
514      describe 'invalid password' do
515        it "should return an error with an invalid password" do
516          post :create, params: {
517            login: user.username, password: 'sssss'
518          }, format: :json
519
520          expect(::JSON.parse(response.body)['error']).to eq(
521            I18n.t("login.incorrect_username_email_or_password")
522          )
523        end
524      end
525
526      describe 'invalid password' do
527        it "should return an error with an invalid password if too long" do
528          User.any_instance.expects(:confirm_password?).never
529          post :create, params: {
530            login: user.username, password: ('s' * (User.max_password_length + 1))
531          }, format: :json
532
533          expect(::JSON.parse(response.body)['error']).to eq(
534            I18n.t("login.incorrect_username_email_or_password")
535          )
536        end
537      end
538
539      describe 'suspended user' do
540        it 'should return an error' do
541          user.suspended_till = 2.days.from_now
542          user.suspended_at = Time.now
543          user.save!
544          StaffActionLogger.new(user).log_user_suspend(user, "<strike>banned</strike>")
545
546          post :create, params: {
547            login: user.username, password: 'myawesomepassword'
548          }, format: :json
549
550          expect(JSON.parse(response.body)['error']).to eq(I18n.t('login.suspended_with_reason',
551            date: I18n.l(user.suspended_till, format: :date_only),
552            reason: Rack::Utils.escape_html(user.suspend_reason)
553          ))
554        end
555      end
556
557      describe 'deactivated user' do
558        it 'should return an error' do
559          User.any_instance.stubs(:active).returns(false)
560
561          post :create, params: {
562            login: user.username, password: 'myawesomepassword'
563          }, format: :json
564
565          expect(JSON.parse(response.body)['error']).to eq(I18n.t('login.not_activated'))
566        end
567      end
568
569      describe 'success by username' do
570        it 'logs in correctly' do
571          events = DiscourseEvent.track_events do
572            post :create, params: {
573              login: user.username, password: 'myawesomepassword'
574            }, format: :json
575          end
576
577          expect(events.map { |event| event[:event_name] }).to include(
578            :user_logged_in, :user_first_logged_in
579          )
580
581          user.reload
582
583          expect(session[:current_user_id]).to eq(user.id)
584          expect(user.user_auth_tokens.count).to eq(1)
585          expect(UserAuthToken.hash_token(cookies[:_t])).to eq(user.user_auth_tokens.first.auth_token)
586        end
587      end
588
589      context 'when user has 2-factor logins' do
590        let!(:user_second_factor) { Fabricate(:user_second_factor, user: user) }
591
592        describe 'when second factor token is missing' do
593          it 'should return the right response' do
594            post :create, params: {
595              login: user.username,
596              password: 'myawesomepassword',
597            }, format: :json
598
599            expect(JSON.parse(response.body)['error']).to eq(I18n.t(
600              'login.invalid_second_factor_code'
601            ))
602          end
603        end
604
605        describe 'when second factor token is invalid' do
606          it 'should return the right response' do
607            post :create, params: {
608              login: user.username,
609              password: 'myawesomepassword',
610              second_factor_token: '00000000'
611            }, format: :json
612
613            expect(JSON.parse(response.body)['error']).to eq(I18n.t(
614              'login.invalid_second_factor_code'
615            ))
616          end
617        end
618
619        describe 'when second factor token is valid' do
620          it 'should log the user in' do
621            post :create, params: {
622              login: user.username,
623              password: 'myawesomepassword',
624              second_factor_token: ROTP::TOTP.new(user_second_factor.data).now
625            }, format: :json
626
627            user.reload
628
629            expect(session[:current_user_id]).to eq(user.id)
630            expect(user.user_auth_tokens.count).to eq(1)
631
632            expect(UserAuthToken.hash_token(cookies[:_t]))
633              .to eq(user.user_auth_tokens.first.auth_token)
634          end
635        end
636      end
637
638      describe 'with a blocked IP' do
639        before do
640          screened_ip = Fabricate(:screened_ip_address)
641          ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(screened_ip.ip_address)
642          post :create, params: {
643            login: "@" + user.username, password: 'myawesomepassword'
644          }, format: :json
645
646          user.reload
647        end
648
649        it "doesn't log in" do
650          expect(session[:current_user_id]).to be_nil
651        end
652      end
653
654      describe 'strips leading @ symbol' do
655        before do
656          post :create, params: {
657            login: "@" + user.username, password: 'myawesomepassword'
658          }, format: :json
659
660          user.reload
661        end
662
663        it 'sets a session id' do
664          expect(session[:current_user_id]).to eq(user.id)
665        end
666      end
667
668      describe 'also allow login by email' do
669        before do
670          post :create, params: {
671            login: user.email, password: 'myawesomepassword'
672          }, format: :json
673        end
674
675        it 'sets a session id' do
676          expect(session[:current_user_id]).to eq(user.id)
677        end
678      end
679
680      context 'login has leading and trailing space' do
681        let(:username) { " #{user.username} " }
682        let(:email) { " #{user.email} " }
683
684        it "strips spaces from the username" do
685          post :create, params: {
686            login: username, password: 'myawesomepassword'
687          }, format: :json
688
689          expect(::JSON.parse(response.body)['error']).not_to be_present
690        end
691
692        it "strips spaces from the email" do
693          post :create, params: {
694            login: email, password: 'myawesomepassword'
695          }, format: :json
696
697          expect(::JSON.parse(response.body)['error']).not_to be_present
698        end
699      end
700
701      describe "when the site requires approval of users" do
702        before do
703          SiteSetting.expects(:must_approve_users?).returns(true)
704        end
705
706        context 'with an unapproved user' do
707          before do
708            post :create, params: {
709              login: user.email, password: 'myawesomepassword'
710            }, format: :json
711          end
712
713          it "doesn't log in the user" do
714            expect(session[:current_user_id]).to be_blank
715          end
716
717          it "shows the 'not approved' error message" do
718            expect(JSON.parse(response.body)['error']).to eq(
719              I18n.t('login.not_approved')
720            )
721          end
722        end
723
724        context "with an unapproved user who is an admin" do
725          before do
726            User.any_instance.stubs(:admin?).returns(true)
727
728            post :create, params: {
729              login: user.email, password: 'myawesomepassword'
730            }, format: :json
731          end
732
733          it 'sets a session id' do
734            expect(session[:current_user_id]).to eq(user.id)
735          end
736        end
737      end
738
739      context 'when admins are restricted by ip address' do
740        let(:permitted_ip_address) { '111.234.23.11' }
741        before do
742          Fabricate(:screened_ip_address, ip_address: permitted_ip_address, action_type: ScreenedIpAddress.actions[:allow_admin])
743          SiteSetting.use_admin_ip_whitelist = true
744        end
745
746        it 'is successful for admin at the ip address' do
747          User.any_instance.stubs(:admin?).returns(true)
748          ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address)
749
750          post :create, params: {
751            login: user.username, password: 'myawesomepassword'
752          }, format: :json
753
754          expect(session[:current_user_id]).to eq(user.id)
755        end
756
757        it 'returns an error for admin not at the ip address' do
758          User.any_instance.stubs(:admin?).returns(true)
759          ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12")
760
761          post :create, params: {
762            login: user.username, password: 'myawesomepassword'
763          }, format: :json
764
765          expect(JSON.parse(response.body)['error']).to be_present
766          expect(session[:current_user_id]).not_to eq(user.id)
767        end
768
769        it 'is successful for non-admin not at the ip address' do
770          User.any_instance.stubs(:admin?).returns(false)
771          ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12")
772
773          post :create, params: {
774            login: user.username, password: 'myawesomepassword'
775          }, format: :json
776
777          expect(session[:current_user_id]).to eq(user.id)
778        end
779      end
780    end
781
782    context 'when email has not been confirmed' do
783      def post_login
784        post :create, params: {
785          login: user.email, password: 'myawesomepassword'
786        }, format: :json
787      end
788
789      it "doesn't log in the user" do
790        post_login
791        expect(session[:current_user_id]).to be_blank
792      end
793
794      it "shows the 'not activated' error message" do
795        post_login
796        expect(JSON.parse(response.body)['error']).to eq(
797          I18n.t 'login.not_activated'
798        )
799      end
800
801      context "and the 'must approve users' site setting is enabled" do
802        before { SiteSetting.expects(:must_approve_users?).returns(true) }
803
804        it "shows the 'not approved' error message" do
805          post_login
806          expect(JSON.parse(response.body)['error']).to eq(
807            I18n.t 'login.not_approved'
808          )
809        end
810      end
811    end
812
813    context 'rate limited' do
814      it 'rate limits login' do
815        SiteSetting.max_logins_per_ip_per_hour = 2
816        RateLimiter.enable
817        RateLimiter.clear_all!
818
819        2.times do
820          post :create, params: {
821            login: user.username, password: 'myawesomepassword'
822          }, format: :json
823
824          expect(response).to be_success
825        end
826
827        post :create, params: {
828          login: user.username, password: 'myawesomepassword'
829        }, format: :json
830
831        expect(response.status).to eq(429)
832        json = JSON.parse(response.body)
833        expect(json["error_type"]).to eq("rate_limit")
834      end
835
836      it 'rate limits second factor attempts' do
837        RateLimiter.enable
838        RateLimiter.clear_all!
839
840        3.times do
841          post :create, params: {
842            login: user.username,
843            password: 'myawesomepassword',
844            second_factor_token: '000000'
845          }, format: :json
846
847          expect(response).to be_success
848        end
849
850        post :create, params: {
851          login: user.username,
852          password: 'myawesomepassword',
853          second_factor_token: '000000'
854        }, format: :json
855
856        expect(response.status).to eq(429)
857        json = JSON.parse(response.body)
858        expect(json["error_type"]).to eq("rate_limit")
859      end
860    end
861  end
862
863  describe '.destroy' do
864    before do
865      @user = log_in
866      delete :destroy, params: { id: @user.username }, format: :json
867    end
868
869    it 'removes the session variable' do
870      expect(session[:current_user_id]).to be_blank
871    end
872
873    it 'removes the auth token cookie' do
874      expect(response.cookies["_t"]).to be_blank
875    end
876  end
877
878  describe '.forgot_password' do
879
880    it 'raises an error without a username parameter' do
881      expect do
882        post :forgot_password, format: :json
883      end.to raise_error(ActionController::ParameterMissing)
884    end
885
886    context 'for a non existant username' do
887      it "doesn't generate a new token for a made up username" do
888        expect do
889          post :forgot_password, params: { login: 'made_up' }, format: :json
890        end.not_to change(EmailToken, :count)
891      end
892
893      it "doesn't enqueue an email" do
894        Jobs.expects(:enqueue).with(:user_mail, anything).never
895        post :forgot_password, params: { login: 'made_up' }, format: :json
896      end
897    end
898
899    context 'for an existing username' do
900      let(:user) { Fabricate(:user) }
901
902      context 'local login is disabled' do
903        before do
904          SiteSetting.enable_local_logins = false
905          post :forgot_password, params: { login: user.username }, format: :json
906        end
907        it_behaves_like "failed to continue local login"
908      end
909
910      context 'SSO is enabled' do
911        before do
912          SiteSetting.sso_url = "https://www.example.com/sso"
913          SiteSetting.enable_sso = true
914
915          post :create, params: {
916            login: user.username, password: 'myawesomepassword'
917          }, format: :json
918        end
919        it_behaves_like "failed to continue local login"
920      end
921
922      it "generates a new token for a made up username" do
923        expect do
924          post :forgot_password, params: { login: user.username }, format: :json
925        end.to change(EmailToken, :count)
926      end
927
928      it "enqueues an email" do
929        Jobs.expects(:enqueue).with(:critical_user_email, has_entries(type: :forgot_password, user_id: user.id))
930        post :forgot_password, params: { login: user.username }, format: :json
931      end
932    end
933
934    context 'do nothing to system username' do
935      let(:system) { Discourse.system_user }
936
937      it 'generates no token for system username' do
938        expect do
939          post :forgot_password, params: { login: system.username }, format: :json
940        end.not_to change(EmailToken, :count)
941      end
942
943      it 'enqueues no email' do
944        Jobs.expects(:enqueue).never
945        post :forgot_password, params: { login: system.username }, format: :json
946      end
947    end
948
949    context 'for a staged account' do
950      let!(:staged) { Fabricate(:staged) }
951
952      it 'generates no token for staged username' do
953        expect do
954          post :forgot_password, params: { login: staged.username }, format: :json
955        end.not_to change(EmailToken, :count)
956      end
957
958      it 'enqueues no email' do
959        Jobs.expects(:enqueue).never
960        post :forgot_password, params: { login: staged.username }, format: :json
961      end
962    end
963  end
964
965  describe '#current' do
966    context "when not logged in" do
967      it "retuns 404" do
968        get :current, format: :json
969        expect(response).not_to be_success
970      end
971    end
972
973    context "when logged in" do
974      let!(:user) { log_in }
975
976      it "returns the JSON for the user" do
977        get :current, format: :json
978        expect(response).to be_success
979        json = ::JSON.parse(response.body)
980        expect(json['current_user']).to be_present
981        expect(json['current_user']['id']).to eq(user.id)
982      end
983    end
984  end
985end