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 /lib/redmine/twofa | |
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 'lib/redmine/twofa')
-rw-r--r-- | lib/redmine/twofa/base.rb | 127 | ||||
-rw-r--r-- | lib/redmine/twofa/totp.rb | 68 |
2 files changed, 195 insertions, 0 deletions
diff --git a/lib/redmine/twofa/base.rb b/lib/redmine/twofa/base.rb new file mode 100644 index 000000000..8369c8f6a --- /dev/null +++ b/lib/redmine/twofa/base.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2020 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module Redmine + module Twofa + class Base + def self.inherited(child) + # require-ing a Base subclass will register it as a 2FA scheme + Redmine::Twofa.register_scheme(scheme_name(child), child) + end + + def self.scheme_name(klass = self) + klass.name.demodulize.underscore + end + + def scheme_name + self.class.scheme_name + end + + def initialize(user) + @user = user + end + + def init_pairing! + @user + end + + def confirm_pairing!(code) + # make sure an otp is used + if verify_otp!(code) + @user.update!(twofa_scheme: scheme_name) + deliver_twofa_paired + return true + else + return false + end + end + + def deliver_twofa_paired + Mailer.security_notification( + @user, + User.current, + { + title: :label_my_account, + message: 'twofa_mail_body_security_notification_paired', + # (mis-)use field here as value wouldn't get localized + field: "twofa__#{scheme_name}__name", + url: { controller: 'my', action: 'account' } + } + ).deliver + end + + def destroy_pairing!(code) + if verify!(code) + destroy_pairing_without_verify! + return true + else + return false + end + end + + def destroy_pairing_without_verify! + @user.update!(twofa_scheme: nil) + deliver_twofa_unpaired + end + + def deliver_twofa_unpaired + Mailer.security_notification( + @user, + User.current, + { + title: :label_my_account, + message: 'twofa_mail_body_security_notification_unpaired', + url: { controller: 'my', action: 'account' } + } + ).deliver + end + + def send_code(controller: nil, action: nil) + # return true only if the scheme sends a code to the user + false + end + + def verify!(code) + verify_otp!(code) + end + + def verify_otp!(code) + raise 'not implemented' + end + + # this will only be used on pairing initialization + def init_pairing_view_variables + otp_confirm_view_variables + end + + def otp_confirm_view_variables + { + scheme_name: scheme_name, + resendable: false + } + end + + private + + def allowed_drift + 30 + end + end + end +end diff --git a/lib/redmine/twofa/totp.rb b/lib/redmine/twofa/totp.rb new file mode 100644 index 000000000..ff4fe9cf1 --- /dev/null +++ b/lib/redmine/twofa/totp.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2020 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module Redmine + module Twofa + class Totp < Base + def init_pairing! + @user.update!(twofa_totp_key: ROTP::Base32.random) + # reset the cached totp as the key might have changed + @totp = nil + super + end + + def destroy_pairing_without_verify! + @user.update!(twofa_totp_key: nil, twofa_totp_last_used_at: nil) + # reset the cached totp as the key might have changed + @totp = nil + super + end + + def verify_otp!(code) + # topt codes are white-space-insensitive + code = code.to_s.remove(/[[:space:]]/) + last_verified_at = @user.twofa_totp_last_used_at + verified_at = totp.verify(code.to_s, drift_behind: allowed_drift, after: last_verified_at) + if verified_at + @user.update!(twofa_totp_last_used_at: verified_at) + return true + else + return false + end + end + + def provisioning_uri + totp.provisioning_uri(@user.mail) + end + + def init_pairing_view_variables + super.merge({ + provisioning_uri: provisioning_uri, + totp_key: @user.twofa_totp_key + }) + end + + private + + def totp + @totp ||= ROTP::TOTP.new(@user.twofa_totp_key, issuer: Setting.app_title) + end + end + end +end |