summaryrefslogtreecommitdiffstats
path: root/app/controllers/account_controller.rb
diff options
context:
space:
mode:
authorGo MAEDA <maeda@farend.jp>2020-08-29 06:21:50 +0000
committerGo MAEDA <maeda@farend.jp>2020-08-29 06:21:50 +0000
commit560bca344ae467cda03e758159fbf131d5c49f43 (patch)
tree15b4b2ec74c1d98e28f47453093588b271e18865 /app/controllers/account_controller.rb
parent657ddfef452b145bbbce2970369ce42624dfca8e (diff)
downloadredmine-560bca344ae467cda03e758159fbf131d5c49f43.tar.gz
redmine-560bca344ae467cda03e758159fbf131d5c49f43.zip
Adds two factor authentication support (#1237).
Patch by Felix Schäfer. git-svn-id: http://svn.redmine.org/redmine/trunk@19988 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/controllers/account_controller.rb')
-rw-r--r--app/controllers/account_controller.rb107
1 files changed, 105 insertions, 2 deletions
diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb
index dd6002de0..56f29e30c 100644
--- a/app/controllers/account_controller.rb
+++ b/app/controllers/account_controller.rb
@@ -204,8 +204,98 @@ class AccountController < ApplicationController
redirect_to(home_url)
end
+ before_action :require_active_twofa, :twofa_setup, only: [:twofa_resend, :twofa_confirm, :twofa]
+ before_action :prevent_twofa_session_replay, only: [:twofa_resend, :twofa]
+
+ def twofa_resend
+ # otp resends count toward the maximum of 3 otp entry tries per password entry
+ if session[:twofa_tries_counter] > 3
+ destroy_twofa_session
+ flash[:error] = l('twofa_too_many_tries')
+ redirect_to home_url
+ else
+ if @twofa.send_code(controller: 'account', action: 'twofa')
+ flash[:notice] = l('twofa_code_sent')
+ end
+ redirect_to account_twofa_confirm_path
+ end
+ end
+
+ def twofa_confirm
+ @twofa_view = @twofa.otp_confirm_view_variables
+ end
+
+ def twofa
+ if @twofa.verify!(params[:twofa_code].to_s)
+ destroy_twofa_session
+ handle_active_user(@user)
+ # allow at most 3 otp entry tries per successfull password entry
+ # this allows using anti brute force techniques on the password entry to also
+ # prevent brute force attacks on the one-time password
+ elsif session[:twofa_tries_counter] > 3
+ destroy_twofa_session
+ flash[:error] = l('twofa_too_many_tries')
+ redirect_to home_url
+ else
+ flash[:error] = l('twofa_invalid_code')
+ redirect_to account_twofa_confirm_path
+ end
+ end
+
private
+ def prevent_twofa_session_replay
+ renew_twofa_session(@user)
+ end
+
+ def twofa_setup
+ # twofa sessions are only valid 2 minutes at a time
+ twomind = 0.0014 # a little more than 2 minutes in days
+ @user = Token.find_active_user('twofa_session', session[:twofa_session_token].to_s, twomind)
+ if @user.blank?
+ destroy_twofa_session
+ redirect_to home_url
+ return
+ end
+
+ # copy back_url, autologin back to params where they are expected
+ params[:back_url] ||= session[:twofa_back_url]
+ params[:autologin] ||= session[:twofa_autologin]
+
+ # set locale for the twofa user
+ set_localization(@user)
+
+ @twofa = Redmine::Twofa.for_user(@user)
+ end
+
+ def require_active_twofa
+ Setting.twofa? ? true : deny_access
+ end
+
+ def setup_twofa_session(user, previous_tries=1)
+ token = Token.create(user: user, action: 'twofa_session')
+ session[:twofa_session_token] = token.value
+ session[:twofa_tries_counter] = previous_tries
+ session[:twofa_back_url] = params[:back_url]
+ session[:twofa_autologin] = params[:autologin]
+ end
+
+ # Prevent replay attacks by using each twofa_session_token only for exactly one request
+ def renew_twofa_session(user)
+ twofa_tries = session[:twofa_tries_counter].to_i + 1
+ destroy_twofa_session
+ setup_twofa_session(user, twofa_tries)
+ end
+
+ def destroy_twofa_session
+ # make sure tokens can only be used once server-side to prevent replay attacks
+ Token.find_token('twofa_session', session[:twofa_session_token].to_s).try(:delete)
+ session[:twofa_session_token] = nil
+ session[:twofa_tries_counter] = nil
+ session[:twofa_back_url] = nil
+ session[:twofa_autologin] = nil
+ end
+
def authenticate_user
if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url])
@@ -224,14 +314,27 @@ class AccountController < ApplicationController
else
# Valid user
if user.active?
- successful_authentication(user)
- update_sudo_timestamp! # activate Sudo Mode
+ if user.twofa_active?
+ setup_twofa_session user
+ twofa = Redmine::Twofa.for_user(user)
+ if twofa.send_code(controller: 'account', action: 'twofa')
+ flash[:notice] = l('twofa_code_sent')
+ end
+ redirect_to account_twofa_confirm_path
+ else
+ handle_active_user(user)
+ end
else
handle_inactive_user(user)
end
end
end
+ def handle_active_user(user)
+ successful_authentication(user)
+ update_sudo_timestamp! # activate Sudo Mode
+ end
+
def open_id_authenticate(openid_url)
back_url = signin_url(:autologin => params[:autologin])
authenticate_with_open_id(