| 
  1 | 
  
    # frozen_string_literal: true 
   | 
   | 
  2 | 
  
    
   | 
   | 
  3 | 
  
    # Redmine - project management software 
   | 
   | 
  4 | 
  
    # Copyright (C) 2006-2020  Jean-Philippe Lang 
   | 
   | 
  5 | 
  
    # 
   | 
   | 
  6 | 
  
    # This program is free software; you can redistribute it and/or 
   | 
   | 
  7 | 
  
    # modify it under the terms of the GNU General Public License 
   | 
   | 
  8 | 
  
    # as published by the Free Software Foundation; either version 2 
   | 
   | 
  9 | 
  
    # of the License, or (at your option) any later version. 
   | 
   | 
  10 | 
  
    # 
   | 
   | 
  11 | 
  
    # This program is distributed in the hope that it will be useful, 
   | 
   | 
  12 | 
  
    # but WITHOUT ANY WARRANTY; without even the implied warranty of 
   | 
   | 
  13 | 
  
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
   | 
   | 
  14 | 
  
    # GNU General Public License for more details. 
   | 
   | 
  15 | 
  
    # 
   | 
   | 
  16 | 
  
    # You should have received a copy of the GNU General Public License 
   | 
   | 
  17 | 
  
    # along with this program; if not, write to the Free Software 
   | 
   | 
  18 | 
  
    # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
   | 
   | 
  19 | 
  
    
   | 
   | 
  20 | 
  
    require File.expand_path('../../test_helper', __FILE__)
   | 
   | 
  21 | 
  
    
   | 
   | 
  22 | 
  
    class TwofaTest < Redmine::IntegrationTest 
   | 
   | 
  23 | 
  
      fixtures :projects, :users, :email_addresses 
   | 
   | 
  24 | 
  
    
   | 
   | 
  25 | 
  
      test "should require twofa setup when configured" do 
   | 
   | 
  26 | 
  
        with_settings twofa: "2" do 
   | 
   | 
  27 | 
  
          log_user('jsmith', 'jsmith')
   | 
   | 
  28 | 
  
          follow_redirect! 
   | 
   | 
  29 | 
  
          assert_redirected_to "/my/twofa/totp/activate/confirm" 
   | 
   | 
  30 | 
  
        end 
   | 
   | 
  31 | 
  
      end 
   | 
   | 
  32 | 
  
    
   | 
   | 
  33 | 
  
      test "should generate and accept backup codes" do 
   | 
   | 
  34 | 
  
        log_user('jsmith', 'jsmith')
   | 
   | 
  35 | 
  
        get "/my/account" 
   | 
   | 
  36 | 
  
        assert_response :success 
   | 
   | 
  37 | 
  
        post "/my/twofa/totp/activate/init" 
   | 
   | 
  38 | 
  
        assert_redirected_to "/my/twofa/totp/activate/confirm" 
   | 
   | 
  39 | 
  
        follow_redirect! 
   | 
   | 
  40 | 
  
        assert_response :success 
   | 
   | 
  41 | 
  
    
   | 
   | 
  42 | 
  
        totp = ROTP::TOTP.new User.find_by_login('jsmith').twofa_totp_key
   | 
   | 
  43 | 
  
        post "/my/twofa/totp/activate", params: { twofa_code: totp.now }
   | 
   | 
  44 | 
  
        assert_redirected_to "/my/account" 
   | 
   | 
  45 | 
  
        follow_redirect! 
   | 
   | 
  46 | 
  
        assert_response :success 
   | 
   | 
  47 | 
  
        assert_select '.flash', /Two-factor authentication successfully enabled/i 
   | 
   | 
  48 | 
  
    
   | 
   | 
  49 | 
  
        post "/my/twofa/backup_codes/init" 
   | 
   | 
  50 | 
  
        assert_redirected_to "/my/twofa/backup_codes/confirm" 
   | 
   | 
  51 | 
  
        follow_redirect! 
   | 
   | 
  52 | 
  
        assert_response :success 
   | 
   | 
  53 | 
  
        assert_select 'form', /Please enter your two-factor authentication code/i 
   | 
   | 
  54 | 
  
    
   | 
   | 
  55 | 
  
        post "/my/twofa/backup_codes/create", params: { twofa_code: "wrong" }
   | 
   | 
  56 | 
  
        assert_redirected_to "/my/twofa/backup_codes/confirm" 
   | 
   | 
  57 | 
  
        follow_redirect! 
   | 
   | 
  58 | 
  
        assert_response :success 
   | 
   | 
  59 | 
  
        assert_select 'form', /Please enter your two-factor authentication code/i 
   | 
   | 
  60 | 
  
    
   | 
   | 
  61 | 
  
        # prevent replay attack prevention from kicking in 
   | 
   | 
  62 | 
  
        User.find_by_login('jsmith').update_column :twofa_totp_last_used_at, 2.minutes.ago.to_i
   | 
   | 
  63 | 
  
    
   | 
   | 
  64 | 
  
        post "/my/twofa/backup_codes/create", params: { twofa_code: totp.now }
   | 
   | 
  65 | 
  
        assert_redirected_to "/my/twofa/backup_codes" 
   | 
   | 
  66 | 
  
        follow_redirect! 
   | 
   | 
  67 | 
  
        assert_response :success 
   | 
   | 
  68 | 
  
        assert_select ".flash", /your backup codes have been generated/i 
   | 
   | 
  69 | 
  
    
   | 
   | 
  70 | 
  
        assert code = response.body.scan(/<code>([a-z0-9]{4} [a-z0-9]{4} [a-z0-9]{4})<\/code>/).flatten.first
   | 
   | 
  71 | 
  
    
   | 
   | 
  72 | 
  
        post "/logout" 
   | 
   | 
  73 | 
  
        follow_redirect! 
   | 
   | 
  74 | 
  
        # prevent replay attack prevention from kicking in 
   | 
   | 
  75 | 
  
        User.find_by_login('jsmith').update_column :twofa_totp_last_used_at, 2.minutes.ago.to_i
   | 
   | 
  76 | 
  
    
   | 
   | 
  77 | 
  
        # sign in with backup code 
   | 
   | 
  78 | 
  
        get "/login" 
   | 
   | 
  79 | 
  
        assert_nil session[:user_id] 
   | 
   | 
  80 | 
  
        assert_response :success 
   | 
   | 
  81 | 
  
        post "/login", params: {
   | 
   | 
  82 | 
  
          username: 'jsmith', 
   | 
   | 
  83 | 
  
          password: 'jsmith' 
   | 
   | 
  84 | 
  
        } 
   | 
   | 
  85 | 
  
        assert_redirected_to "/account/twofa/confirm" 
   | 
   | 
  86 | 
  
        follow_redirect! 
   | 
   | 
  87 | 
  
    
   | 
   | 
  88 | 
  
        assert_select "#login-form h3", /two-factor authentication/i 
   | 
   | 
  89 | 
  
        post "/account/twofa", params: { twofa_code: code }
   | 
   | 
  90 | 
  
        assert_redirected_to "/my/page" 
   | 
   | 
  91 | 
  
        follow_redirect! 
   | 
   | 
  92 | 
  
        assert_response :success 
   | 
   | 
  93 | 
  
      end 
   | 
   | 
  94 | 
  
    
   | 
   | 
  95 | 
  
      test "should configure totp and require code on login" do 
   | 
   | 
  96 | 
  
        with_settings twofa: "2" do 
   | 
   | 
  97 | 
  
          log_user('jsmith', 'jsmith')
   | 
   | 
  98 | 
  
          follow_redirect! 
   | 
   | 
  99 | 
  
          assert_redirected_to "/my/twofa/totp/activate/confirm" 
   | 
   | 
  100 | 
  
          follow_redirect! 
   | 
   | 
  101 | 
  
    
   | 
   | 
  102 | 
  
          assert key = User.find_by_login('jsmith').twofa_totp_key
   | 
   | 
  103 | 
  
          assert key.present? 
   | 
   | 
  104 | 
  
          totp = ROTP::TOTP.new key 
   | 
   | 
  105 | 
  
    
   | 
   | 
  106 | 
  
          post "/my/twofa/totp/activate", params: { twofa_code: '123456789' }
   | 
   | 
  107 | 
  
          assert_redirected_to "/my/twofa/totp/activate/confirm" 
   | 
   | 
  108 | 
  
          follow_redirect! 
   | 
   | 
  109 | 
  
    
   | 
   | 
  110 | 
  
          post "/my/twofa/totp/activate", params: { twofa_code: totp.now }
   | 
   | 
  111 | 
  
          assert_redirected_to "/my/account" 
   | 
   | 
  112 | 
  
    
   | 
   | 
  113 | 
  
          post "/logout" 
   | 
   | 
  114 | 
  
          follow_redirect! 
   | 
   | 
  115 | 
  
    
   | 
   | 
  116 | 
  
          # prevent replay attack prevention from kicking in 
   | 
   | 
  117 | 
  
          User.find_by_login('jsmith').update_column :twofa_totp_last_used_at, 2.minutes.ago.to_i
   | 
   | 
  118 | 
  
    
   | 
   | 
  119 | 
  
          # sign in with totp 
   | 
   | 
  120 | 
  
          get "/login" 
   | 
   | 
  121 | 
  
          assert_nil session[:user_id] 
   | 
   | 
  122 | 
  
          assert_response :success 
   | 
   | 
  123 | 
  
          post "/login", params: {
   | 
   | 
  124 | 
  
            username: 'jsmith', 
   | 
   | 
  125 | 
  
            password: 'jsmith' 
   | 
   | 
  126 | 
  
          } 
   | 
   | 
  127 | 
  
    
   | 
   | 
  128 | 
  
          assert_redirected_to "/account/twofa/confirm" 
   | 
   | 
  129 | 
  
          follow_redirect! 
   | 
   | 
  130 | 
  
    
   | 
   | 
  131 | 
  
          assert_select "#login-form h3", /two-factor authentication/i 
   | 
   | 
  132 | 
  
          post "/account/twofa", params: { twofa_code: 'wrong code' }
   | 
   | 
  133 | 
  
          assert_redirected_to "/account/twofa/confirm" 
   | 
   | 
  134 | 
  
          follow_redirect! 
   | 
   | 
  135 | 
  
          assert_select "#login-form h3", /two-factor authentication/i 
   | 
   | 
  136 | 
  
          assert_select ".flash", /code is invalid/i 
   | 
   | 
  137 | 
  
    
   | 
   | 
  138 | 
  
          post "/account/twofa", params: { twofa_code: totp.now }
   | 
   | 
  139 | 
  
          assert_redirected_to "/my/page" 
   | 
   | 
  140 | 
  
          follow_redirect! 
   | 
   | 
  141 | 
  
          assert_response :success 
   | 
   | 
  142 | 
  
        end 
   | 
   | 
  143 | 
  
      end 
   | 
   | 
  144 | 
  
    end 
   |