diff --git a/CHANGELOG.md b/CHANGELOG.md index 092c191..a409889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.6.0 + +* Depend on redmine_bots instead of redmine_telegram_common +* Google auth confirmation on first page +* Telegram account connection/2fa connection segregation +* Don't send code to locked users + # 1.5.1 * Add redmine_telegram_common dependency to init.rb diff --git a/README.md b/README.md index 823ef3a..f0a7842 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,15 @@ Supports: ## Requirements -- [redmine_telegram_common](https://github.com/centosadmin/redmine_telegram_common) +- [redmine_bots](https://github.com/centosadmin/redmine_bots) - HTTPS host - Telegram Bot Webhook needs to POST on HTTPS hosts. - Ruby 2.3+ +### Upgrade to 1.6.0+ + +From 1.6.0 redmine_2fa depends on [redmine_bots](https://github.com/centosadmin/redmine_bots) instead of redmine_telegram_common. +Please, install it and follow migration instructions from README of redmine_bots. + ### Upgrade from 1.3.4 to 1.4.0+ From 1.4.0 redmine_2fa (as well as other Southbridge telegram plugins) is using bot from redmine_telegram_common. diff --git a/README.ru.md b/README.ru.md index 9ebf818..5c258a8 100644 --- a/README.ru.md +++ b/README.ru.md @@ -17,10 +17,15 @@ ## Требования -- [redmine_telegram_common](https://github.com/centosadmin/redmine_telegram_common) +- [redmine_bots](https://github.com/centosadmin/redmine_bots) - HTTPS - нужен для того, чтобы принимать сообщение от Telegram Bot API ([веб-хук](https://tlgrm.ru/docs/bots/api#setwebhook)) - Ruby 2.3+ +### Обновление до 1.6.0 и выше + +Начиная с версии 1.6.0 redmine_2fa зависит от [redmine_bots](https://github.com/centosadmin/redmine_bots) вместо redmine_telegram_common. +Пожалуйста, установите redmine_bots и следуйте инструкциям по миграции данных в его README. + ### Обновление с 1.3.4 до 1.4.0+ Начиная с версии 1.4.0 redmine_2fa (так же, как и другие telegram-плагины от Southbridge) использует бота из redmine_telegram_common. diff --git a/app/models/redmine_2fa/telegram_connection.rb b/app/models/redmine_2fa/telegram_connection.rb new file mode 100644 index 0000000..19dff2f --- /dev/null +++ b/app/models/redmine_2fa/telegram_connection.rb @@ -0,0 +1,5 @@ +module Redmine2FA + class TelegramConnection < ActiveRecord::Base + belongs_to :user + end +end diff --git a/app/views/account/init_2fa/_google_auth.html.erb b/app/views/account/init_2fa/_google_auth.html.erb index 2568661..5a5f922 100644 --- a/app/views/account/init_2fa/_google_auth.html.erb +++ b/app/views/account/init_2fa/_google_auth.html.erb @@ -6,8 +6,16 @@ - - - <%= submit_tag t('redmine_2fa.second_authentications.next_button_html'), onclick: "$('#init2FAForm').submit();" %> - + <%= form_tag(confirm_otp_path) do %> + + + + + <%= text_field_tag :otp_code, nil, autocomplete: 'off', autofocus: true %> + + <%= hidden_field_tag :protocol, 'google_auth' %> + + + + <% end %> diff --git a/app/views/account/init_2fa/_telegram.html.erb b/app/views/account/init_2fa/_telegram.html.erb index 69c5a53..583270e 100644 --- a/app/views/account/init_2fa/_telegram.html.erb +++ b/app/views/account/init_2fa/_telegram.html.erb @@ -1,3 +1,3 @@
- <%= render partial: 'telegram_login/widget' %> + <%= render partial: 'telegram_login/widget', locals: { context: '2fa_connection' } %>
diff --git a/app/views/account/otp.html.erb b/app/views/account/otp.html.erb index ae1f8c1..dd41246 100644 --- a/app/views/account/otp.html.erb +++ b/app/views/account/otp.html.erb @@ -15,7 +15,7 @@ - <%= t('redmine_2fa.resend.instruction_html', timeout: 30, bot_name: Setting.plugin_redmine_telegram_common['bot_name']) %> + <%= t('redmine_2fa.resend.instruction_html', timeout: 30) %> diff --git a/app/views/account/telegram_login.html.erb b/app/views/account/telegram_login.html.erb new file mode 100644 index 0000000..cbc6283 --- /dev/null +++ b/app/views/account/telegram_login.html.erb @@ -0,0 +1 @@ +<%= render partial: 'account/init_2fa/telegram' %> diff --git a/config/locales/en.yml b/config/locales/en.yml index b7facac..360534c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -39,12 +39,13 @@ en: instruction: Please choose type of two-factor authentication disable: "Do not use" next_button_html: Next » + next_input_html: Next; google_auth: instruction_html: |

Please setup Google Authenticator follow the instruction.

Use this QR-code in application.

- next_step_instruction: After app setup click "Next". + next_step_instruction: After app setup, enter the confirmation code and click "Next". telegram: instruction_html: | %{bot_name} will send you authentication codes. Please activate it.
diff --git a/config/locales/ru.yml b/config/locales/ru.yml index c0ded7e..f6b0be3 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -40,12 +40,13 @@ ru: instruction: Пожалуйста, выберите способ двухфакторной аутентификации disable: "Не использовать" next_button_html: Далее » + next_input_html: Далее google_auth: instruction_html: |

Установите приложение Google Authenticator следуя инструкции по ссылке.

Используйте предложенный ниже QR-код в приложении.

- next_step_instruction: После настройки приложения, нажмите "Далее". + next_step_instruction: После настройки приложения введите код подтверждения и нажмите "Далее". telegram: instruction_html: | Бот "%{bot_name}" будет отправлять вам коды авторизации.
diff --git a/db/migrate/010_create_telegram_connections.rb b/db/migrate/010_create_telegram_connections.rb new file mode 100644 index 0000000..46192a3 --- /dev/null +++ b/db/migrate/010_create_telegram_connections.rb @@ -0,0 +1,9 @@ +class CreateTelegramConnections < ActiveRecord::Migration + def change + create_table :redmine_2fa_telegram_connections do |t| + t.belongs_to :user, index: true, foreign_key: true + t.integer :telegram_id + end + add_index :redmine_2fa_telegram_connections, :telegram_id + end +end \ No newline at end of file diff --git a/db/migrate/011_transfer_telegram_connections.rb b/db/migrate/011_transfer_telegram_connections.rb new file mode 100644 index 0000000..5bd339a --- /dev/null +++ b/db/migrate/011_transfer_telegram_connections.rb @@ -0,0 +1,8 @@ +class TransferTelegramConnections < ActiveRecord::Migration + def up + User.where(two_fa_id: Redmine2FA::AuthSource::Telegram.first.id).each do |user| + next unless user.telegram_account + Redmine2FA::TelegramConnection.create!(user_id: user.id, telegram_id: user.telegram_account.telegram_id) + end + end +end \ No newline at end of file diff --git a/init.rb b/init.rb index d5739d5..a2c24cd 100644 --- a/init.rb +++ b/init.rb @@ -1,4 +1,4 @@ -require_dependency Rails.root.join('plugins','redmine_telegram_common', 'init') +require_dependency Rails.root.join('plugins','redmine_bots', 'init') FileUtils.mkdir_p(Rails.root.join('log/redmine_2fa')) unless Dir.exist?(Rails.root.join('log/redmine_2fa')) @@ -23,7 +23,7 @@ Redmine::Plugin.register :redmine_2fa do name 'Redmine 2FA' - version '1.5.1' + version '1.6.0' url 'https://github.com/centosadmin/redmine_2fa' description 'Two-factor authorization for Redmine' author 'Southbridge' @@ -31,10 +31,9 @@ requires_redmine version_or_higher: '3.0' - requires_redmine_plugin :redmine_telegram_common, '0.7.0' + requires_redmine_plugin :redmine_bots, '0.1.0' - settings(default: { 'bot_token' => '', - 'required' => false, + settings(default: { 'required' => false, 'active_protocols' => Redmine2FA::AVAILABLE_PROTOCOLS }, partial: 'settings/redmine_2fa') diff --git a/lib/redmine_2fa.rb b/lib/redmine_2fa.rb index 661c840..662a2fc 100644 --- a/lib/redmine_2fa.rb +++ b/lib/redmine_2fa.rb @@ -21,18 +21,10 @@ def self.switched_off? active_protocols.size.zero? || active_protocols.size == 1 && active_protocols.include?('none') end - def self.bot_token - Setting.plugin_redmine_telegram_common['bot_token'] - end - def self.logger Logger.new(Rails.root.join('log', 'redmine_2fa', 'bot-update.log')) end - def self.handle_message(message) - TelegramCommon::Bot.new(bot_token, message).call if message.is_a?(Telegram::Bot::Types::Message) - end - module Configuration def self.configuration Redmine::Configuration['redmine_2fa'] diff --git a/lib/redmine_2fa/code_sender/telegram_sender.rb b/lib/redmine_2fa/code_sender/telegram_sender.rb deleted file mode 100644 index 1cfd65c..0000000 --- a/lib/redmine_2fa/code_sender/telegram_sender.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Redmine2FA - class CodeSender::TelegramSender < CodeSender - attr_reader :errors, :user - - def initialize(user) - @user = user - @telegram_account = user.telegram_account - @errors = [] - end - - def send_message - if @telegram_account.present? && @telegram_account.active? - token = Redmine2FA.bot_token - bot = Telegram::Bot::Client.new(token) - - message = I18n.t('redmine_2fa.telegram_auth.message', - app_title: Setting.app_title, code: code, expiration_time: timestamp) - - bot.api.send_message(chat_id: @telegram_account.telegram_id, text: message) - end - rescue Telegram::Bot::Exceptions::ResponseError => e - errors << "Telegram Bot API: #{e.message}" - end - end -end diff --git a/lib/redmine_2fa/patches/account_controller_patch/confirm_methods.rb b/lib/redmine_2fa/patches/account_controller_patch/confirm_methods.rb index 1cb8c6c..7416f35 100644 --- a/lib/redmine_2fa/patches/account_controller_patch/confirm_methods.rb +++ b/lib/redmine_2fa/patches/account_controller_patch/confirm_methods.rb @@ -27,6 +27,7 @@ def confirm_2fa def confirm_otp if @user.authenticate_otp(params[:otp_code], drift: 120) + update_two_fa if @user.two_fa.nil? reset_otp_session successful_authentication(@user) else diff --git a/lib/redmine_2fa/patches/account_controller_patch/second_authentication_init.rb b/lib/redmine_2fa/patches/account_controller_patch/second_authentication_init.rb index afdbbfa..ff215f0 100644 --- a/lib/redmine_2fa/patches/account_controller_patch/second_authentication_init.rb +++ b/lib/redmine_2fa/patches/account_controller_patch/second_authentication_init.rb @@ -5,7 +5,7 @@ module SecondAuthenticationInit private def password_authentication - if Redmine2FA.switched_off? || @user.ignore_2fa? || @user.two_factor_authenticable? + if Redmine2FA.switched_off? || @user.locked? || @user.ignore_2fa? || @user.two_factor_authenticable? super else begin diff --git a/lib/redmine_2fa/patches/account_controller_patch/second_authentication_step.rb b/lib/redmine_2fa/patches/account_controller_patch/second_authentication_step.rb index 11077e2..2684ce9 100644 --- a/lib/redmine_2fa/patches/account_controller_patch/second_authentication_step.rb +++ b/lib/redmine_2fa/patches/account_controller_patch/second_authentication_step.rb @@ -5,10 +5,10 @@ module SecondAuthenticationStep private def password_authentication - if Redmine2FA.switched_on? && !@user.ignore_2fa? && @user.two_factor_authenticable? + if Redmine2FA.switched_on? && !@user.locked? && !@user.ignore_2fa? && @user.two_factor_authenticable? send_code flash[:error] = sender.errors.join(', ') if sender.errors.present? - render(@user.two_fa&.name == 'Telegram' ? 'telegram_login/index' : 'account/otp') + render(@user.two_fa&.name == 'Telegram' ? 'account/telegram_login' : 'account/otp') else super end diff --git a/lib/redmine_2fa/patches/user_patch.rb b/lib/redmine_2fa/patches/user_patch.rb index 81b0961..067e69b 100644 --- a/lib/redmine_2fa/patches/user_patch.rb +++ b/lib/redmine_2fa/patches/user_patch.rb @@ -14,6 +14,7 @@ def self.included(base) alias_method_chain :update_hashed_password, :otp_auth belongs_to :two_fa, class_name: 'AuthSource' + has_one :telegram_connection, class_name: 'Redmine2FA::TelegramConnection' end end @@ -44,9 +45,12 @@ def google_authenticable? def reset_second_auth otp_regenerate_secret - self.two_fa_id = nil - self.ignore_2fa = false - save! + self.class.transaction do + self.telegram_connection&.destroy! + self.two_fa_id = nil + self.ignore_2fa = false + save! + end end def confirm_mobile_phone(code) diff --git a/test/functional/redmine_telegram_connections_controller_test.rb b/test/functional/redmine_telegram_connections_controller_test.rb deleted file mode 100644 index 0447ce1..0000000 --- a/test/functional/redmine_telegram_connections_controller_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class RedmineTelegramConnectionsControllerTest < ActionController::TestCase - fixtures :users, :email_addresses, :roles, :auth_sources - - setup do - @user = User.find(2) - @telegram_account = TelegramCommon::Account.create(telegram_id: 123) - end - - context 'connect with valid data' do - setup do - post :create, - user_id: 2, user_email: @user.mail, - telegram_id: 123, token: @telegram_account.token, - plugin: 'plugin_redmine_2fa' - end - - should 'set telegram account to user' do - @telegram_account.reload - assert_equal @user, @telegram_account.user - end - - should 'set telegram auth source to user' do - @user.reload - assert_equal auth_sources(:telegram), @user.two_fa - end - end -end diff --git a/test/unit/redmine_2fa/code_sender/telegram_sender_test.rb b/test/unit/redmine_2fa/code_sender/telegram_sender_test.rb deleted file mode 100644 index 2affb7b..0000000 --- a/test/unit/redmine_2fa/code_sender/telegram_sender_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require File.expand_path('../../../../test_helper', __FILE__) - -class Redmine2FA::CodeSender::TelegramSenderTest < ActiveSupport::TestCase - fixtures :users, :email_addresses, :roles, :auth_sources - setup do - User.any_instance.stubs(:otp_code).returns('123456') - - @user = User.find(2) - @telegram_auth_source = auth_sources(:telegram) - @user.auth_source = @telegram_auth_source - end - - # context 'initialize with user' do - # - # should 'store user' do - # @sender = Redmine2FA::CodeSender.new(@user) - # assert_equal @user, @sender.user - # end - # should 'store code' do - # @sender = Redmine2FA::CodeSender.new(@user) - # assert_equal '123456', @sender.code - # end - # - # should 'store timestamp' do - # Timecop.freeze(Time.parse('12:00:00 UTC')) do - # @sender = Redmine2FA::CodeSender.new(@user) - # assert_equal '12:02:00', @sender.timestamp - # end - # end - # - # - # end -end diff --git a/test/unit/redmine_2fa/code_sender_test.rb b/test/unit/redmine_2fa/code_sender_test.rb index 3302af8..0908f8d 100644 --- a/test/unit/redmine_2fa/code_sender_test.rb +++ b/test/unit/redmine_2fa/code_sender_test.rb @@ -14,12 +14,6 @@ class Redmine2FA::CodeSenderTest < ActiveSupport::TestCase end context 'define sender' do - should 'be TelegramSender' do - @user.two_fa = auth_sources(:telegram) - @sender = Redmine2FA::CodeSender.new(@user) - assert @sender.sender.is_a?(Redmine2FA::CodeSender::NullSender) - end - should 'be SMSSender' do @user.two_fa = auth_sources(:sms) @sender = Redmine2FA::CodeSender.new(@user) diff --git a/test/unit/telegram_common/bot_test.rb b/test/unit/telegram_common/bot_test.rb deleted file mode 100644 index e9da1c7..0000000 --- a/test/unit/telegram_common/bot_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) -require 'telegram/bot' - -class TelegramCommon::BotTest < ActiveSupport::TestCase - fixtures :users, :email_addresses, :roles, :auth_sources - - setup do - Redmine2FA.stubs(:bot_token) - Telegram::Bot::Api.any_instance.stubs(:get_me) - end - - context '/start' do - setup do - @telegram_message = ActionController::Parameters.new( - from: { - id: 123, - username: 'dhh', - first_name: 'David', - last_name: 'Haselman' - }, - chat: { - id: 123, - type: 'private' - }, - text: '/start' - ) - - @bot_service = TelegramCommon::Bot.new(Redmine2FA.bot_token, @telegram_message) - end - - context 'with user' do - setup do - TelegramCommon::Bot.any_instance - .expects(:send_message) - .with(I18n.t('telegram_common.bot.start.hello')) - - @user = User.find(2) - @telegram_account = TelegramCommon::Account.create(telegram_id: 123, user_id: @user.id) - - @bot_service.call - end - - should 'set telegram auth source' do - @user.reload - assert_equal auth_sources(:telegram), @user.two_fa - end - end - end -end diff --git a/travis.sh b/travis.sh index 0664865..df1ce5d 100644 --- a/travis.sh +++ b/travis.sh @@ -40,7 +40,7 @@ mv $TESTSPACE/additional_environment.rb config/ # add telegram_common plugin -git clone git://github.com/centosadmin/redmine_telegram_common.git $PATH_TO_REDMINE/plugins/redmine_telegram_common +git clone git://github.com/centosadmin/redmine_bots.git $PATH_TO_REDMINE/plugins/redmine_bots # create a link to the backlogs plugin ln -sf $PATH_TO_PLUGIN plugins/$NAME_OF_PLUGIN