diff options
author | Go MAEDA <maeda@farend.jp> | 2020-08-29 06:21:50 +0000 |
---|---|---|
committer | Go MAEDA <maeda@farend.jp> | 2020-08-29 06:21:50 +0000 |
commit | 560bca344ae467cda03e758159fbf131d5c49f43 (patch) | |
tree | 15b4b2ec74c1d98e28f47453093588b271e18865 /app/controllers/account_controller.rb | |
parent | 657ddfef452b145bbbce2970369ce42624dfca8e (diff) | |
download | redmine-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.rb | 107 |
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( |