diff --git a/Gemfile b/Gemfile
index e549386..0f023ab 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,10 @@ gem 'sidekiq-rate-limiter', git: 'https://github.com/centosadmin/sidekiq-rate-li
gem 'telegram-bot-ruby', '~> 0.8.6'
gem 'slack-ruby-bot'
gem 'celluloid-io'
-gem 'tdlib-ruby', '~> 0.9'
+gem 'tdlib-ruby', '~> 1.0'
+gem 'jwt'
+gem 'filelock'
+gem 'lazy_object', '~> 0.0.2'
group :test do
gem 'timecop'
diff --git a/app/controllers/redmine_telegram_setup_controller.rb b/app/controllers/redmine_telegram_setup_controller.rb
index 783e830..2b53557 100644
--- a/app/controllers/redmine_telegram_setup_controller.rb
+++ b/app/controllers/redmine_telegram_setup_controller.rb
@@ -19,7 +19,7 @@ def authorize
authenticate.(params)
save_phone_settings(phone_number: params['phone_number'])
redirect_to plugin_settings_path('redmine_bots'), notice: t('telegram_common.client.authorize.success')
- rescue RedmineBots::Telegram::TdlibAuthenticate::AuthenticationError => e
+ rescue RedmineBots::Telegram::Tdlib::Authenticate::AuthenticationError => e
redirect_to plugin_settings_path('redmine_bots'), alert: e.message
end
end
diff --git a/app/controllers/telegram_login_controller.rb b/app/controllers/telegram_login_controller.rb
index f2f072a..a3d43a3 100644
--- a/app/controllers/telegram_login_controller.rb
+++ b/app/controllers/telegram_login_controller.rb
@@ -4,12 +4,33 @@ def index
def check_auth
user = User.find_by_id(session[:otp_user_id]) || User.current
+ auth = RedmineBots::Telegram::Bot::Authenticate.(user, login_params, context: context)
- auth = RedmineBots::Telegram::Bot::Authenticate.(user, login_params)
+ handle_auth_result(auth, user)
+ end
+
+ def send_sign_in_link
+ user = session[:otp_user_id] ? User.find(session[:otp_user_id]) : User.current
+ RedmineBots::Telegram::Bot::SendSignInLink.(user, context: context, params: params.slice(:autologin, :back_url))
+ end
+
+ def check_jwt
+ user = User.find_by_id(session[:otp_user_id]) || User.current
+ auth = RedmineBots::Telegram::Bot::AuthenticateByToken.(user, params[:token], context: context)
+
+ handle_auth_result(auth, user)
+ end
+ private
+
+ def context
+ session[:otp_user_id] ? '2fa_connection' : 'account_connection'
+ end
+
+ def handle_auth_result(auth, user)
if auth.success?
- if session[:otp_user_id]
- user.update_column(:two_fa_id, AuthSource.find_by_name('Telegram').id)
+ if user != User.current
+ user.update!(two_fa_id: AuthSource.find_by_name('Telegram').id)
successful_authentication(user)
else
redirect_to my_page_path, notice: t('redmine_bots.telegram.bot.login.success')
@@ -19,8 +40,6 @@ def check_auth
end
end
- private
-
def login_params
params.permit(:id, :first_name, :last_name, :username, :photo_url, :auth_date, :hash)
end
diff --git a/app/views/settings/redmine_bots/_telegram.erb b/app/views/settings/redmine_bots/_telegram.erb
index def3e4c..83198cf 100644
--- a/app/views/settings/redmine_bots/_telegram.erb
+++ b/app/views/settings/redmine_bots/_telegram.erb
@@ -136,6 +136,6 @@
<%= hidden_field_tag 'settings[telegram_bot_name]', @settings['telegram_bot_name'] %>
-<%= hidden_field_tag 'settings[telegram_bot_id]', @settings['bot_id'] %>
+<%= hidden_field_tag 'settings[telegram_bot_id]', @settings['telegram_bot_id'] %>
<%= hidden_field_tag 'settings[telegram_robot_id]', @settings['telegram_robot_id'] %>
<%= hidden_field_tag 'settings[telegram_phone_number]', @settings['telegram_phone_number'] %>
diff --git a/app/views/telegram_login/_widget.erb b/app/views/telegram_login/_widget.erb
index 6347ba9..61504ef 100644
--- a/app/views/telegram_login/_widget.erb
+++ b/app/views/telegram_login/_widget.erb
@@ -1 +1,19 @@
-
+
+<% current_user = @user || User.current %>
+<% telegram_account =
+ case context
+ when 'account_connection'
+ current_user.telegram_account
+ when '2fa_connection'
+ current_user.telegram_connection
+ end
+%>
+
+
+<% if current_user.logged? && telegram_account.present? %>
+ <%= link_to send_telegram_sign_in_link_path(params.slice(:autologin, :back_url)), method: :post, remote: true do %>
+ <%= t('redmine_bots.telegram.bot.login.send_to_telegram') %>
+ <% end %> (<%= t('redmine_bots.telegram.bot.login.widget_not_visible') %>)
+<% else %>
+ <%= t('redmine_bots.telegram.bot.login.write_to_bot', bot: Setting.plugin_redmine_bots['telegram_bot_name']) %>
+<% end %>
diff --git a/app/views/telegram_login/index.html.erb b/app/views/telegram_login/index.html.erb
index 88bc1c5..f6dc774 100644
--- a/app/views/telegram_login/index.html.erb
+++ b/app/views/telegram_login/index.html.erb
@@ -1,3 +1,3 @@
- <%= render partial: 'telegram_login/widget' %>
+ <%= render partial: 'telegram_login/widget', locals: { context: 'account_connection' } %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fab0a79..0597bde 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -58,6 +58,7 @@ en:
start: "Start work with bot"
connect: "Connect Redmine and Telegram account"
help: "Help about commands"
+ token: Get auth link
login:
success: Authentication succeed
errors:
@@ -66,6 +67,11 @@ en:
hash_outdated: Request is outdated
wrong_account: Wrong Telegram account
not_persisted: Failed to persist Telegram account data
+ invalid_token: Token is invalid
+ follow_link: Please, follow link
+ send_to_telegram: Send auth link to Telegram
+ widget_not_visible: if widget isn not visible
+ write_to_bot: "Please, write command /token to bot @%{bot} if widget is not visible"
slack:
commands:
connect: connect Slack account with Redmine
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index e693dda..5bc0c2b 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -58,6 +58,7 @@ ru:
start: "Начало работы с ботом"
connect: "Связывание аккаунтов Redmine и Telegram"
help: "Справка по командам"
+ token: Получить ссылку для аутентификации
login:
success: Аутентификация прошла успешно
errors:
@@ -66,6 +67,11 @@ ru:
hash_outdated: Истекло время ожидания
wrong_account: Неверный аккаунт Telegram
not_persisted: Не удалось сохранить данные о Telegram-аккаунте
+ invalid_token: Неверный token
+ follow_link: Пожалуйста, проследуйте по ссылке
+ send_to_telegram: Отправить ссылку в Telegram
+ widget_not_visible: если виджет недоступен
+ write_to_bot: "Пожалуйста, напишите команду /token боту @%{bot}, если не видно виджет"
slack:
commands:
connect: связывание аккаунтов Slack и Redmine
diff --git a/config/routes.rb b/config/routes.rb
index b745a35..cd37f2e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -21,4 +21,6 @@
get 'login' => 'telegram_login#index', as: 'telegram_login'
get 'check_auth' => 'telegram_login#check_auth'
+ get 'check_jwt' => 'telegram_login#check_jwt'
+ post 'send_sign_in_link' => 'telegram_login#send_sign_in_link', as: 'send_telegram_sign_in_link'
end
diff --git a/lib/redmine_bots/result.rb b/lib/redmine_bots/result.rb
new file mode 100644
index 0000000..b94f02b
--- /dev/null
+++ b/lib/redmine_bots/result.rb
@@ -0,0 +1,17 @@
+module RedmineBots
+ class Result
+ attr_reader :value
+
+ def initialize(success, value)
+ @success, @value = success, value
+ end
+
+ def success?
+ @success
+ end
+
+ def failure?
+ !success?
+ end
+ end
+end
diff --git a/lib/redmine_bots/telegram.rb b/lib/redmine_bots/telegram.rb
index 2c04584..6650467 100644
--- a/lib/redmine_bots/telegram.rb
+++ b/lib/redmine_bots/telegram.rb
@@ -2,10 +2,6 @@ module RedmineBots::Telegram
extend Tdlib::DependencyProviders::GetMe
extend Tdlib::DependencyProviders::AddBot
- def self.table_name_prefix
- 'telegram_common_'
- end
-
def self.set_locale
I18n.locale = Setting['default_language']
end
diff --git a/lib/redmine_bots/telegram/bot.rb b/lib/redmine_bots/telegram/bot.rb
index 6686d47..a572580 100644
--- a/lib/redmine_bots/telegram/bot.rb
+++ b/lib/redmine_bots/telegram/bot.rb
@@ -5,6 +5,7 @@ class Bot
include BotCommand::Start
include BotCommand::Connect
include BotCommand::Help
+ include BotCommand::Token
attr_reader :bot_token, :logger, :command
diff --git a/lib/redmine_bots/telegram/bot/authenticate.rb b/lib/redmine_bots/telegram/bot/authenticate.rb
index 6291637..6c8deed 100644
--- a/lib/redmine_bots/telegram/bot/authenticate.rb
+++ b/lib/redmine_bots/telegram/bot/authenticate.rb
@@ -1,77 +1,80 @@
-module RedmineBots::Telegram
- class Bot::Authenticate
- AUTH_TIMEOUT = 60.minutes
+module RedmineBots
+ module Telegram
+ class Bot::Authenticate
+ AUTH_TIMEOUT = 60.minutes
- def self.call(user, auth_data)
- new(user, auth_data).call
- end
-
- def initialize(user, auth_data)
- @user, @auth_data = user, Hash[auth_data.sort_by { |k, _| k }]
- end
+ def self.call(user, auth_data, context:)
+ new(user, auth_data, context: context).call
+ end
- def call
- return failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_logged')) unless @user.logged?
- return failure(I18n.t('redmine_bots.telegram.bot.login.errors.hash_invalid')) unless hash_valid?
- return failure(I18n.t('redmine_bots.telegram.bot.login.errors.hash_outdated')) unless up_to_date?
+ def initialize(user, auth_data, context:)
+ @user, @auth_data, @context = user, Hash[auth_data.sort_by { |k, _| k }], context
+ end
- telegram_account = TelegramAccount.find_by(user_id: @user.id)
+ def call
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_logged')) unless @user.logged?
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.hash_invalid')) unless hash_valid?
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.hash_outdated')) unless up_to_date?
- if telegram_account.present?
- if telegram_account.telegram_id
- unless @auth_data['id'].to_i == telegram_account.telegram_id
- return failure(I18n.t('redmine_bots.telegram.bot.login.errors.wrong_account'))
- end
+ case @context
+ when '2fa_connection'
+ telegram_account = prepare_telegram_account(model_class: Redmine2FA::TelegramConnection)
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.wrong_account')) unless telegram_account
+ when 'account_connection'
+ telegram_account = prepare_telegram_account(model_class: TelegramAccount)
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.wrong_account')) unless telegram_account
+ telegram_account.assign_attributes(@auth_data.slice('first_name', 'last_name', 'username'))
else
- telegram_account.telegram_id = @auth_data['id']
+ return failure('Invalid context')
end
- else
- telegram_account = TelegramAccount.find_or_initialize_by(telegram_id: @auth_data['id'])
- if telegram_account.user_id
- unless telegram_account.user_id == @user.id
- return failure(I18n.t('redmine_bots.telegram.bot.login.errors.wrong_account'))
- end
+
+ if telegram_account.save
+ success(telegram_account)
else
- telegram_account.user_id = @user.id
+ failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_persisted'))
end
end
- telegram_account.assign_attributes(@auth_data.slice(*%w[first_name last_name username]))
-
- if telegram_account.save
- success(telegram_account)
- else
- failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_persisted'))
- end
- end
-
- private
+ private
- def hash_valid?
- Utils.auth_hash(@auth_data) == @auth_data['hash']
- end
-
- def up_to_date?
- Time.at(@auth_data['auth_date'].to_i) > Time.now - AUTH_TIMEOUT
- end
+ def prepare_telegram_account(model_class:)
+ telegram_account = model_class.find_by(user_id: @user.id)
- def success(value)
- Result.new(true, value)
- end
+ if telegram_account.present?
+ if telegram_account.telegram_id
+ unless @auth_data['id'].to_i == telegram_account.telegram_id
+ return nil
+ end
+ else
+ telegram_account.telegram_id = @auth_data['id']
+ end
+ else
+ telegram_account = model_class.find_or_initialize_by(telegram_id: @auth_data['id'])
+ if telegram_account.user_id
+ unless telegram_account.user_id == @user.id
+ return nil
+ end
+ else
+ telegram_account.user_id = @user.id
+ end
+ end
+ telegram_account
+ end
- def failure(value)
- Result.new(false, value)
- end
+ def hash_valid?
+ Utils.auth_hash(@auth_data) == @auth_data['hash']
+ end
- class Result
- attr_reader :value
+ def up_to_date?
+ Time.at(@auth_data['auth_date'].to_i) > Time.now - AUTH_TIMEOUT
+ end
- def initialize(success, value)
- @success, @value = success, value
+ def success(value)
+ Result.new(true, value)
end
- def success?
- @success
+ def failure(value)
+ Result.new(false, value)
end
end
end
diff --git a/lib/redmine_bots/telegram/bot/authenticate_by_token.rb b/lib/redmine_bots/telegram/bot/authenticate_by_token.rb
new file mode 100644
index 0000000..dae3c92
--- /dev/null
+++ b/lib/redmine_bots/telegram/bot/authenticate_by_token.rb
@@ -0,0 +1,70 @@
+module RedmineBots
+ module Telegram
+ class Bot::AuthenticateByToken
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def initialize(user, token, context:)
+ @user, @token, @context = user, token, context
+ end
+
+ def call
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_logged')) if @user.anonymous?
+
+ case @context
+ when '2fa_connection'
+ telegram_account = prepare_telegram_account(model_class: Redmine2FA::TelegramConnection)
+ when 'account_connection'
+ telegram_account = prepare_telegram_account(model_class: TelegramAccount)
+ else
+ return failure('Invalid context')
+ end
+
+ return failure(I18n.t('redmine_bots.telegram.bot.login.errors.wrong_account')) unless telegram_account
+
+ if telegram_account.save
+ success(telegram_account)
+ else
+ failure(I18n.t('redmine_bots.telegram.bot.login.errors.not_persisted'))
+ end
+ rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::InvalidIssuerError, JWT::InvalidIatError
+ failure(I18n.t('redmine_bots.telegram.bot.login.errors.invalid_token'))
+ end
+
+ def prepare_telegram_account(model_class:)
+ telegram_data = Jwt.decode_token(@token).first
+ telegram_id = telegram_data['telegram_id'].to_i
+ telegram_account = model_class.find_by(user_id: @user.id)
+
+ if telegram_account.present?
+ if telegram_account.telegram_id
+ unless telegram_id == telegram_account.telegram_id
+ return nil
+ end
+ else
+ telegram_account.telegram_id = telegram_id
+ end
+ else
+ telegram_account = model_class.find_or_initialize_by(telegram_id: telegram_id)
+ if telegram_account.user_id
+ unless telegram_account.user_id == @user.id
+ return nil
+ end
+ else
+ telegram_account.user_id = @user.id
+ end
+ end
+ telegram_account
+ end
+
+ def success(value)
+ Result.new(true, value)
+ end
+
+ def failure(value)
+ Result.new(false, value)
+ end
+ end
+ end
+end
diff --git a/lib/redmine_bots/telegram/bot/send_sign_in_link.rb b/lib/redmine_bots/telegram/bot/send_sign_in_link.rb
new file mode 100644
index 0000000..95b7301
--- /dev/null
+++ b/lib/redmine_bots/telegram/bot/send_sign_in_link.rb
@@ -0,0 +1,35 @@
+module RedmineBots
+ class Telegram::Bot::SendSignInLink
+ include RedmineBots::Telegram::Jwt
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def initialize(user, context:, params: {})
+ @user, @context, @params = user, context, params
+ end
+
+ def call
+ telegram_account =
+ case @context
+ when '2fa_connection'
+ @user.telegram_account
+ when 'account_connection'
+ @user.telegram_connection
+ else
+ nil
+ end
+ return unless telegram_account
+
+ token = encode(telegram_id: telegram_account.telegram_id)
+ message_params = {
+ chat_id: telegram_account.telegram_id,
+ message: "#{I18n.t('redmine_bots.telegram.bot.login.follow_link')}: #{Setting.protocol}://#{Setting.host_name}/telegram/check_jwt?#{{ token: token }.merge(@params).to_query}",
+ bot_token: RedmineBots::Telegram.bot_token
+ }
+
+ RedmineBots::Telegram::Bot::MessageSender.call(message_params)
+ end
+ end
+end
diff --git a/lib/redmine_bots/telegram/bot_command/help.rb b/lib/redmine_bots/telegram/bot_command/help.rb
index 107ac52..c91ff2f 100644
--- a/lib/redmine_bots/telegram/bot_command/help.rb
+++ b/lib/redmine_bots/telegram/bot_command/help.rb
@@ -9,7 +9,7 @@ def help
private
def private_commands
- %w(start connect help)
+ %w(start connect help token)
end
def group_commands
diff --git a/lib/redmine_bots/telegram/bot_command/token.rb b/lib/redmine_bots/telegram/bot_command/token.rb
new file mode 100644
index 0000000..62f2d95
--- /dev/null
+++ b/lib/redmine_bots/telegram/bot_command/token.rb
@@ -0,0 +1,10 @@
+module RedmineBots::Telegram
+ module BotCommand
+ module Token
+ def token
+ token = Jwt.encode(telegram_id: chat_id)
+ send_message("#{I18n.t('redmine_bots.telegram.bot.login.follow_link')}: #{Setting.protocol}://#{Setting.host_name}/telegram/check_jwt?token=#{token}")
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/redmine_bots/telegram/jwt.rb b/lib/redmine_bots/telegram/jwt.rb
new file mode 100644
index 0000000..614e939
--- /dev/null
+++ b/lib/redmine_bots/telegram/jwt.rb
@@ -0,0 +1,24 @@
+module RedmineBots::Telegram
+ module Jwt
+ extend self
+
+ def encode(payload)
+ exp = Time.now.to_i + 300
+ JWT.encode({ **payload, iss: issuer, exp: exp }, secret)
+ end
+
+ def decode_token(token)
+ JWT.decode(token, secret, true, algorithm: 'HS256', iss: issuer, verify_iss: true)
+ end
+
+ private
+
+ def secret
+ Rails.application.config.secret_key_base
+ end
+
+ def issuer
+ Setting.host_name
+ end
+ end
+end
diff --git a/lib/redmine_bots/telegram/tdlib/add_bot.rb b/lib/redmine_bots/telegram/tdlib/add_bot.rb
index b6600ae..104a6d9 100644
--- a/lib/redmine_bots/telegram/tdlib/add_bot.rb
+++ b/lib/redmine_bots/telegram/tdlib/add_bot.rb
@@ -2,8 +2,8 @@ module RedmineBots::Telegram::Tdlib
class AddBot < Command
def call(bot_id)
@client.on_ready do |client|
- chat = client.broadcast_and_receive('@type' => 'createPrivateChat', 'user_id' => bot_id)
- client.broadcast_and_receive('@type' => 'sendBotStartMessage',
+ chat = client.fetch('@type' => 'createPrivateChat', 'user_id' => bot_id)
+ client.fetch('@type' => 'sendBotStartMessage',
'bot_user_id' => bot_id,
'chat_id' => chat['id'])
end
diff --git a/lib/redmine_bots/telegram/tdlib/authenticate.rb b/lib/redmine_bots/telegram/tdlib/authenticate.rb
index 4abc87f..990be0d 100644
--- a/lib/redmine_bots/telegram/tdlib/authenticate.rb
+++ b/lib/redmine_bots/telegram/tdlib/authenticate.rb
@@ -1,6 +1,6 @@
module RedmineBots::Telegram::Tdlib
class Authenticate < Command
- TIMEOUT = 10
+ TIMEOUT = 20
class AuthenticationError < StandardError
end
@@ -18,17 +18,16 @@ def call(params)
auth_state = update.dig('authorization_state', '@type')
end
- Timeout.timeout(TIMEOUT) do
- loop do
- case auth_state
- when 'authorizationStateWaitPhoneNumber'
- set_phone(params['phone_number'])
- when 'authorizationStateWaitCode'
- return unless params.key?('phone_code')
- check_code(params['phone_code'])
- when 'authorizationStateReady'
- return
- end
+ loop do
+ case auth_state
+ when 'authorizationStateWaitPhoneNumber'
+ set_phone(params['phone_number'])
+ when 'authorizationStateWaitCode'
+ return unless params.key?('phone_code')
+ check_code(params['phone_code'])
+ when 'authorizationStateReady'
+ fetch_all_chats
+ return
end
end
rescue Timeout::Error
@@ -40,12 +39,25 @@ def call(params)
private
+ def fetch_all_chats
+ offset_order = 2**63 - 1
+ offset_chat_id = 0
+ limit = 100
+
+ loop do
+ chat_ids = @client.fetch('@type' => 'getChats', 'offset_order' => offset_order, 'offset_chat_id' => offset_chat_id, 'limit' => limit).tap(&error_handler)['chat_ids']
+ break if chat_ids.empty?
+ last_chat = @client.fetch('@type' => 'getChat', 'chat_id' => chat_ids.last).tap(&error_handler)
+ offset_chat_id, offset_order = last_chat.values_at('id', 'order')
+ end
+ end
+
def set_phone(phone)
params = {
'@type' => 'setAuthenticationPhoneNumber',
'phone_number' => phone
}
- @client.broadcast_and_receive(params).tap(&error_handler)
+ @client.fetch(params).tap(&error_handler)
end
def check_code(code)
@@ -53,7 +65,7 @@ def check_code(code)
'@type' => 'checkAuthenticationCode',
'code' => code
}
- @client.broadcast_and_receive(params).tap(&error_handler)
+ @client.fetch(params).tap(&error_handler)
end
def error_handler
diff --git a/lib/redmine_bots/telegram/tdlib/close_chat.rb b/lib/redmine_bots/telegram/tdlib/close_chat.rb
index d37c8b6..4fa52a2 100644
--- a/lib/redmine_bots/telegram/tdlib/close_chat.rb
+++ b/lib/redmine_bots/telegram/tdlib/close_chat.rb
@@ -2,12 +2,12 @@ module RedmineBots::Telegram::Tdlib
class CloseChat < Command
def call(chat_id)
@client.on_ready do |client|
- me = client.broadcast_and_receive('@type' => 'getMe')
+ me = client.fetch('@type' => 'getMe')
bot_id = Setting.find_by(name: 'plugin_redmine_bots').value['telegram_bot_id'].to_i
- chat = client.broadcast_and_receive('@type' => 'getChat', 'chat_id' => chat_id)
+ chat = client.fetch('@type' => 'getChat', 'chat_id' => chat_id)
- group_info = client.broadcast_and_receive('@type' => 'getBasicGroupFullInfo',
+ group_info = client.fetch('@type' => 'getBasicGroupFullInfo',
'basic_group_id' => chat.dig('type', 'basic_group_id')
)
return if group_info['@type'] == 'error'
@@ -24,7 +24,7 @@ def call(chat_id)
private
def delete_member(chat_id, user_id)
- @client.broadcast_and_receive('@type' => 'setChatMemberStatus',
+ @client.fetch('@type' => 'setChatMemberStatus',
'chat_id' => chat_id,
'user_id' => user_id,
'status' => { '@type' => 'chatMemberStatusLeft' })
diff --git a/lib/redmine_bots/telegram/tdlib/command.rb b/lib/redmine_bots/telegram/tdlib/command.rb
index 8948f97..07a9ddd 100644
--- a/lib/redmine_bots/telegram/tdlib/command.rb
+++ b/lib/redmine_bots/telegram/tdlib/command.rb
@@ -10,15 +10,15 @@ def initialize(client)
module Callable
def call(*)
- begin
- tries ||= 3
+ tries ||= 3
+ Filelock Rails.root.join('tmp', 'redmine_bots', 'tdlib_lock'), wait: 10 do
super
- rescue Timeout::Error
- sleep 2
- retry unless (tries -= 1).zero?
- ensure
- @client.close
end
+ rescue Timeout::Error
+ sleep 2
+ retry unless (tries -= 1).zero?
+ ensure
+ @client.close
end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/create_chat.rb b/lib/redmine_bots/telegram/tdlib/create_chat.rb
index c5ca15d..7142a02 100644
--- a/lib/redmine_bots/telegram/tdlib/create_chat.rb
+++ b/lib/redmine_bots/telegram/tdlib/create_chat.rb
@@ -3,18 +3,18 @@ class CreateChat < Command
def call(title, user_ids)
@client.on_ready(timeout: 5) do |client|
user_ids.each do |id|
- client.broadcast_and_receive('@type' => 'getUser', 'user_id' => id)
+ client.fetch('@type' => 'getUser', 'user_id' => id)
end
sleep 1
- chat = client.broadcast_and_receive('@type' => 'createNewBasicGroupChat',
+ chat = client.fetch('@type' => 'createNewBasicGroupChat',
'title' => title,
'user_ids' => user_ids)
sleep 1
- client.broadcast_and_receive('@type' => 'toggleBasicGroupAdministrators',
+ client.fetch('@type' => 'toggleBasicGroupAdministrators',
'basic_group_id' => chat.dig('type', 'basic_group_id'),
'everyone_is_administrator' => false)
chat
diff --git a/lib/redmine_bots/telegram/tdlib/dependency_providers.rb b/lib/redmine_bots/telegram/tdlib/dependency_providers.rb
index e19f6c3..bef9d71 100644
--- a/lib/redmine_bots/telegram/tdlib/dependency_providers.rb
+++ b/lib/redmine_bots/telegram/tdlib/dependency_providers.rb
@@ -2,22 +2,24 @@ module RedmineBots::Telegram::Tdlib
module DependencyProviders
module Client
def client
- settings = Setting.plugin_redmine_bots
- TD::Api.set_log_file_path(Rails.root.join('log', 'redmine_bots', 'tdlib.log').to_s)
- config = {
- api_id: settings['telegram_api_id'],
- api_hash: settings['telegram_api_hash'],
- database_directory: Rails.root.join('tmp', 'redmine_bots', 'tdlib', 'db').to_s,
- files_directory: Rails.root.join('tmp', 'redmine_bots', 'tdlib', 'files').to_s,
- }
- proxy = {
- '@type' => 'proxySocks5',
- 'server' => settings['tdlib_proxy_server'],
- 'port' => settings['tdlib_proxy_port'],
- 'username' => settings['tdlib_proxy_user'],
- 'password' => settings['tdlib_proxy_password']
- }
- TD::Client.new(**(settings['tdlib_use_proxy'] ? { proxy: proxy } : {}), **config)
+ LazyObject.new do
+ settings = Setting.plugin_redmine_bots
+ TD::Api.set_log_file_path(Rails.root.join('log', 'redmine_bots', 'tdlib.log').to_s)
+ config = {
+ api_id: settings['telegram_api_id'],
+ api_hash: settings['telegram_api_hash'],
+ database_directory: Rails.root.join('tmp', 'redmine_bots', 'tdlib', 'db').to_s,
+ files_directory: Rails.root.join('tmp', 'redmine_bots', 'tdlib', 'files').to_s,
+ }
+ proxy = {
+ '@type' => 'proxySocks5',
+ 'server' => settings['tdlib_proxy_server'],
+ 'port' => settings['tdlib_proxy_port'],
+ 'username' => settings['tdlib_proxy_user'],
+ 'password' => settings['tdlib_proxy_password']
+ }
+ TD::Client.new(**(settings['tdlib_use_proxy'] ? { proxy: proxy } : {}), **config)
+ end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/get_chat.rb b/lib/redmine_bots/telegram/tdlib/get_chat.rb
index 871b619..b877f9b 100644
--- a/lib/redmine_bots/telegram/tdlib/get_chat.rb
+++ b/lib/redmine_bots/telegram/tdlib/get_chat.rb
@@ -2,7 +2,7 @@ module RedmineBots::Telegram::Tdlib
class GetChat < Command
def call(id)
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'getChat', 'chat_id' => id)
+ client.fetch('@type' => 'getChat', 'chat_id' => id)
end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/get_chat_link.rb b/lib/redmine_bots/telegram/tdlib/get_chat_link.rb
index d64fd05..b891288 100644
--- a/lib/redmine_bots/telegram/tdlib/get_chat_link.rb
+++ b/lib/redmine_bots/telegram/tdlib/get_chat_link.rb
@@ -2,7 +2,7 @@ module RedmineBots::Telegram::Tdlib
class GetChatLink < Command
def call(chat_id)
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'generateChatInviteLink', 'chat_id' => chat_id)
+ client.fetch('@type' => 'generateChatInviteLink', 'chat_id' => chat_id)
end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/get_me.rb b/lib/redmine_bots/telegram/tdlib/get_me.rb
index fd6381d..d92f87e 100644
--- a/lib/redmine_bots/telegram/tdlib/get_me.rb
+++ b/lib/redmine_bots/telegram/tdlib/get_me.rb
@@ -2,7 +2,7 @@ module RedmineBots::Telegram::Tdlib
class GetMe < Command
def call
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'getMe')
+ client.fetch('@type' => 'getMe')
end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/get_user.rb b/lib/redmine_bots/telegram/tdlib/get_user.rb
index 93cc9db..63132f9 100644
--- a/lib/redmine_bots/telegram/tdlib/get_user.rb
+++ b/lib/redmine_bots/telegram/tdlib/get_user.rb
@@ -2,7 +2,7 @@ module RedmineBots::Telegram::Tdlib
class GetUser < Command
def call(user_id)
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'getUser', 'user_id' => user_id)
+ client.fetch('@type' => 'getUser', 'user_id' => user_id)
end
end
end
diff --git a/lib/redmine_bots/telegram/tdlib/rename_chat.rb b/lib/redmine_bots/telegram/tdlib/rename_chat.rb
index 2d4355c..0f335cb 100644
--- a/lib/redmine_bots/telegram/tdlib/rename_chat.rb
+++ b/lib/redmine_bots/telegram/tdlib/rename_chat.rb
@@ -2,7 +2,7 @@ module RedmineBots::Telegram::Tdlib
class RenameChat < Command
def call(chat_id, new_title)
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'setChatTitle',
+ client.fetch('@type' => 'setChatTitle',
'chat_id' => chat_id,
'title' => new_title)
end
diff --git a/lib/redmine_bots/telegram/tdlib/toggle_chat_admin.rb b/lib/redmine_bots/telegram/tdlib/toggle_chat_admin.rb
index c138aee..3cdf71c 100644
--- a/lib/redmine_bots/telegram/tdlib/toggle_chat_admin.rb
+++ b/lib/redmine_bots/telegram/tdlib/toggle_chat_admin.rb
@@ -15,8 +15,8 @@ def call(chat_id, user_id, admin = true)
{ '@type' => 'chatMemberStatusMember' }
end
@client.on_ready do |client|
- client.broadcast_and_receive('@type' => 'getUser', 'user_id' => user_id)
- client.broadcast_and_receive('@type' => 'setChatMemberStatus',
+ client.fetch('@type' => 'getUser', 'user_id' => user_id)
+ client.fetch('@type' => 'setChatMemberStatus',
'chat_id' => chat_id,
'user_id' => user_id,
'status' => status)
diff --git a/lib/redmine_bots/telegram/update_manager.rb b/lib/redmine_bots/telegram/update_manager.rb
index c503c89..4578d9c 100644
--- a/lib/redmine_bots/telegram/update_manager.rb
+++ b/lib/redmine_bots/telegram/update_manager.rb
@@ -1,5 +1,5 @@
class RedmineBots::Telegram::UpdateManager
- COMMON_COMMANDS = %w[help start connect]
+ COMMON_COMMANDS = %w[help start connect token]
def initialize
@handlers = []
diff --git a/test/fixtures/telegram_common_accounts.yml b/test/fixtures/telegram_accounts.yml
similarity index 100%
rename from test/fixtures/telegram_common_accounts.yml
rename to test/fixtures/telegram_accounts.yml
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 01768b9..32e0979 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -7,7 +7,7 @@
require "minitest/reporters"
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
-ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', %i[telegram_common_accounts users], telegram_common_accounts: TelegramAccount)
+ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', %i[telegram_accounts users])
class ActiveSupport::TestCase
extend Minitest::Spec::DSL
diff --git a/test/unit/redmine_bots/bot/authenticate_by_token_test.rb b/test/unit/redmine_bots/bot/authenticate_by_token_test.rb
new file mode 100644
index 0000000..f404c87
--- /dev/null
+++ b/test/unit/redmine_bots/bot/authenticate_by_token_test.rb
@@ -0,0 +1,76 @@
+require File.expand_path('../../../../test_helper', __FILE__)
+
+class RedmineBots::Telegram::Bot::AuthenticateByTokenTest < ActiveSupport::TestCase
+ fixtures :telegram_accounts, :users
+
+ let(:described_class) { RedmineBots::Telegram::Bot::AuthenticateByToken }
+
+ context 'when user is anonymous' do
+ it 'returns failure result' do
+ result = described_class.new(users(:anonymous), 'token', context: 'account_connection').call
+
+ expect(result.success?).must_equal false
+ expect(result.value).must_equal "You're not logged in"
+ end
+ end
+
+ context 'when token is invalid' do
+ it 'returns failure result' do
+ result = described_class.new(users(:logged), 'invalid_token', context: 'account_connection').call
+
+ expect(result.success?).must_equal false
+ expect(result.value).must_equal 'Token is invalid'
+ end
+ end
+
+ context 'when telegram account found by user_id' do
+ context 'when telegram ids do not match' do
+ it 'returns failure result' do
+ RedmineBots::Telegram::Jwt.stubs(:decode_token).returns([{ 'telegram_id' => 2 }, {}])
+
+ result = described_class.new(users(:logged), 'token', context: 'account_connection').call
+
+ expect(result.success?).must_equal false
+ expect(result.value).must_equal "Wrong Telegram account"
+ end
+ end
+
+ context 'when telegram ids match' do
+ it 'updates attributes and returns successful result' do
+ RedmineBots::Telegram::Jwt.stubs(:decode_token).returns([{ 'telegram_id' => 1 }, {}])
+
+ result = described_class.new(users(:logged), 'token', context: 'account_connection').call
+
+ account = TelegramAccount.find(1)
+
+ expect(result.value).must_equal account
+ expect(result.success?).must_equal true
+ end
+ end
+ end
+
+ context 'when telegram account not found by user_id' do
+ context 'when user ids do not match' do
+ it 'returns failure result' do
+ RedmineBots::Telegram::Jwt.stubs(:decode_token).returns([{ 'telegram_id' => 1 }, {}])
+ result = described_class.new(users(:user_3), 'token', context: 'account_connection').call
+
+ expect(result.success?).must_equal false
+ expect(result.value).must_equal "Wrong Telegram account"
+ end
+ end
+
+ context 'when telegram account does not have user_id' do
+ it 'updates attributes and returns successful result' do
+ RedmineBots::Telegram::Jwt.stubs(:decode_token).returns([{ 'telegram_id' => 3 }, {}])
+ result = described_class.new(users(:user_3), 'token', context: 'account_connection').call
+
+ account = TelegramAccount.last
+
+ expect(result.value).must_equal account
+ expect(account.user_id).must_equal users(:user_3).id
+ expect(result.success?).must_equal true
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/test/unit/redmine_bots/bot/authenticate_test.rb b/test/unit/redmine_bots/bot/authenticate_test.rb
index b9a612d..406ee55 100644
--- a/test/unit/redmine_bots/bot/authenticate_test.rb
+++ b/test/unit/redmine_bots/bot/authenticate_test.rb
@@ -1,7 +1,7 @@
require File.expand_path('../../../../test_helper', __FILE__)
class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
- fixtures :telegram_common_accounts, :users
+ fixtures :telegram_accounts, :users
let(:described_class) { RedmineBots::Telegram::Bot::Authenticate }
@@ -11,7 +11,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when user is anonymous' do
it 'returns failure result' do
- result = described_class.new(users(:anonymous), {}).call
+ result = described_class.new(users(:anonymous), {}, context: 'account_connection').call
expect(result.success?).must_equal false
expect(result.value).must_equal "You're not logged in"
@@ -20,7 +20,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when hash is not valid' do
it 'returns failure result' do
- result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'wrong_hash', 'auth_date' => Time.now.to_i }).call
+ result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'wrong_hash', 'auth_date' => Time.now.to_i }, context: 'account_connection').call
expect(result.success?).must_equal false
expect(result.value).must_equal "Hash is invalid"
@@ -29,7 +29,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when hash is outdated' do
it 'returns failure result' do
- result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => (Time.now - 61.minutes).to_i }).call
+ result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => (Time.now - 61.minutes).to_i }, context: 'account_connection').call
expect(result.success?).must_equal false
expect(result.value).must_equal "Request is outdated"
@@ -39,7 +39,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when telegram account found by user_id' do
context 'when telegram ids do not match' do
it 'returns failure result' do
- result = described_class.new(users(:logged), { 'id' => 2, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }).call
+ result = described_class.new(users(:logged), { 'id' => 2, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }, context: 'account_connection').call
expect(result.success?).must_equal false
expect(result.value).must_equal "Wrong Telegram account"
@@ -48,7 +48,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when telegram ids match' do
it 'updates attributes and returns successful result' do
- result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }).call
+ result = described_class.new(users(:logged), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }, context: 'account_connection').call
account = TelegramAccount.find(1)
@@ -63,7 +63,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when telegram account not found by user_id' do
context 'when user ids do not match' do
it 'returns failure result' do
- result = described_class.new(users(:user_3), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }).call
+ result = described_class.new(users(:user_3), { 'id' => 1, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }, context: 'account_connection').call
expect(result.success?).must_equal false
expect(result.value).must_equal "Wrong Telegram account"
@@ -72,7 +72,7 @@ class RedmineBots::Telegram::Bot::AuthenticateTest < ActiveSupport::TestCase
context 'when telegram account does not have user_id' do
it 'updates attributes and returns successful result' do
- result = described_class.new(users(:user_3), { 'id' => 3, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }).call
+ result = described_class.new(users(:user_3), { 'id' => 3, 'first_name' => 'test', 'last_name' => 'test', 'hash' => 'auth_hash', 'auth_date' => Time.now.to_i }, context: 'account_connection').call
account = TelegramAccount.last
diff --git a/test/unit/redmine_bots/bot_test.rb b/test/unit/redmine_bots/bot_test.rb
index 9f317a0..2cbb4af 100644
--- a/test/unit/redmine_bots/bot_test.rb
+++ b/test/unit/redmine_bots/bot_test.rb
@@ -107,6 +107,7 @@ class RedmineBots::Telegram::BotTest < ActiveSupport::TestCase
/start - #{I18n.t('redmine_bots.telegram.bot.private.help.start')}
/connect - #{I18n.t('redmine_bots.telegram.bot.private.help.connect')}
/help - #{I18n.t('redmine_bots.telegram.bot.private.help.help')}
+ /token - #{I18n.t('redmine_bots.telegram.bot.private.help.token')}
TEXT
message = text.chomp
@@ -143,6 +144,7 @@ class RedmineBots::Telegram::BotTest < ActiveSupport::TestCase
/start - #{I18n.t('redmine_bots.telegram.bot.private.help.start')}
/connect - #{I18n.t('redmine_bots.telegram.bot.private.help.connect')}
/help - #{I18n.t('redmine_bots.telegram.bot.private.help.help')}
+ /token - #{I18n.t('redmine_bots.telegram.bot.private.help.token')}
TEXT
message = text.chomp