From 5345c145efb3e75b484dc02c2b3ba9d348259b9e Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Thu, 27 Aug 2020 00:39:23 +0900 Subject: [PATCH 2/4] Adds a setting to disable/enable/require 2fa auth (#1237). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch by Felix Felix Schäfer. --- app/controllers/application_controller.rb | 30 ++++++++++++++++++++- app/controllers/twofa_controller.rb | 18 +++++++++---- app/models/setting.rb | 6 +++++ app/models/user.rb | 4 +++ app/views/my/account.html.erb | 2 ++ app/views/settings/_authentication.html.erb | 11 ++++++++ app/views/twofa/activate_confirm.html.erb | 2 ++ app/views/twofa/select_scheme.html.erb | 19 +++++++++++++ app/views/users/_form.html.erb | 2 ++ config/locales/de.yml | 5 ++++ config/locales/en.yml | 6 +++++ config/routes.rb | 2 ++ config/settings.yml | 3 +++ lib/redmine/twofa.rb | 5 ++++ 14 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 app/views/twofa/select_scheme.html.erb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fca9ebc90..08dea30a7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -56,7 +56,7 @@ class ApplicationController < ActionController::Base end end - before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change + before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change, :check_twofa_activation after_action :record_project_usage rescue_from ::Unauthorized, :with => :deny_access @@ -89,6 +89,9 @@ class ApplicationController < ActionController::Base if user.must_change_password? session[:pwd] = '1' end + if user.must_activate_twofa? + session[:must_activate_twofa] = '1' + end end def user_setup @@ -205,6 +208,31 @@ class ApplicationController < ActionController::Base end end + def init_twofa_pairing_and_send_code_for(twofa) + twofa.init_pairing! + if twofa.send_code(controller: 'twofa', action: 'activate') + flash[:notice] = l('twofa_code_sent') + end + redirect_to controller: 'twofa', action: 'activate_confirm', scheme: twofa.scheme_name + end + + def check_twofa_activation + if session[:must_activate_twofa] + if User.current.must_activate_twofa? + flash[:warning] = l('twofa_warning_require') + if Redmine::Twofa.available_schemes.length == 1 + twofa_scheme = Redmine::Twofa.for_twofa_scheme(Redmine::Twofa.available_schemes.first) + twofa = twofa_scheme.new(User.current) + init_twofa_pairing_and_send_code_for(twofa) + else + redirect_to controller: 'twofa', action: 'select_scheme' + end + else + session.delete(:must_activate_twofa) + end + end + end + def set_localization(user=User.current) lang = nil if user && user.logged? diff --git a/app/controllers/twofa_controller.rb b/app/controllers/twofa_controller.rb index eba55db82..093b61cc8 100644 --- a/app/controllers/twofa_controller.rb +++ b/app/controllers/twofa_controller.rb @@ -23,16 +23,20 @@ class TwofaController < ApplicationController before_action :require_login before_action :require_admin, only: :admin_deactivate + before_action :require_active_twofa + require_sudo_mode :activate_init, :deactivate_init + skip_before_action :check_twofa_activation, only: [:select_scheme, :activate_init, :activate_confirm, :activate] + + def select_scheme + @user = User.current + end + before_action :activate_setup, only: [:activate_init, :activate_confirm, :activate] def activate_init - @twofa.init_pairing! - if @twofa.send_code(controller: 'twofa', action: 'activate') - flash[:notice] = l('twofa_code_sent') - end - redirect_to action: :activate_confirm, scheme: @twofa.scheme_name + init_twofa_pairing_and_send_code_for(@twofa) end def activate_confirm @@ -103,4 +107,8 @@ class TwofaController < ApplicationController redirect_to my_account_path end end + + def require_active_twofa + Setting.twofa? ? true : deny_access + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index da121263d..e71a9a0cf 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -223,6 +223,12 @@ class Setting < ActiveRecord::Base s end + def self.twofa_from_params(params) + # unpair all current 2FA pairings when switching off 2FA + Redmine::Twofa.unpair_all! if params == '0' && self.twofa? + params + end + # Helper that returns an array based on per_page_options setting def self.per_page_options_array per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort diff --git a/app/models/user.rb b/app/models/user.rb index 5b4089ea4..d905aa1b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -396,6 +396,10 @@ class User < Principal twofa_scheme.present? end + def must_activate_twofa? + Setting.twofa == '2' && !twofa_active? + end + def pref self.preference ||= UserPreference.new(:user => self) end diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index da7746bb2..996bead61 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -28,6 +28,7 @@ <% if Setting.openid? %>

<%= f.text_field :identity_url %>

<% end %> + <% if Setting.twofa? -%>

<% if @user.twofa_active? %> @@ -39,6 +40,7 @@ <% end %> <% end %>

+ <% end -%> <% @user.custom_field_values.select(&:editable?).each do |value| %>

<%= custom_field_tag_with_label :user, value %>

diff --git a/app/views/settings/_authentication.html.erb b/app/views/settings/_authentication.html.erb index 9a39497b8..5522ff5cf 100644 --- a/app/views/settings/_authentication.html.erb +++ b/app/views/settings/_authentication.html.erb @@ -28,6 +28,17 @@

<%= setting_check_box :lost_password %>

+

+ <%= setting_select :twofa, [[l(:label_disabled), "0"], + [l(:label_optional), "1"], + [l(:label_required_lower), "2"]] -%> + + <%= t 'twofa_hint_disabled_html', label: t(:label_disabled) -%>
+ <%= t 'twofa_hint_required_html', label: t(:label_required_lower) -%> +
+

+ +

<%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

diff --git a/app/views/twofa/activate_confirm.html.erb b/app/views/twofa/activate_confirm.html.erb index fc356323c..78194c62b 100644 --- a/app/views/twofa/activate_confirm.html.erb +++ b/app/views/twofa/activate_confirm.html.erb @@ -22,6 +22,8 @@ <% end %> +<% unless @user.must_activate_twofa? %> <% content_for :sidebar do %> <%= render :partial => 'my/sidebar' %> <% end %> +<% end %> diff --git a/app/views/twofa/select_scheme.html.erb b/app/views/twofa/select_scheme.html.erb new file mode 100644 index 000000000..cbc7c3506 --- /dev/null +++ b/app/views/twofa/select_scheme.html.erb @@ -0,0 +1,19 @@ +<%= title l('twofa_label_setup') %> + +<%= form_tag({ controller: 'twofa', action: 'activate_init' }, method: :post) do %> +
+

<%=l 'twofa_notice_select' -%>

+

+ <% Redmine::Twofa.available_schemes.each_with_index do |s, idx| %> + + <% end %> +

+
+

<%= submit_tag l(:label_next).html_safe + " »".html_safe -%>

+<% end %> + +<% unless @user.must_activate_twofa? %> +<% content_for :sidebar do %> +<%= render partial: 'my/sidebar' %> +<% end %> +<% end %> diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index b9054a3b0..ab9f7b541 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -42,6 +42,7 @@

<%= f.check_box :generate_password %>

<%= f.check_box :must_change_passwd %>

+ <% if Setting.twofa? -%>

<% if @user.twofa_active? %> @@ -55,6 +56,7 @@ <%=l 'twofa_not_active' %> <% end %>

+ <% end -%> diff --git a/config/locales/de.yml b/config/locales/de.yml index c86f39533..b588ac2eb 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -719,6 +719,7 @@ de: label_repository_new: Neues Repository label_repository_plural: Repositories label_required: Erforderlich + label_required_lower: erforderlich label_result_plural: Resultate label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge label_revision: Revision @@ -1330,8 +1331,12 @@ de: twofa_currently_active: "Aktiv: %{twofa_scheme_name}" twofa_not_active: "Nicht aktiv" twofa_label_code: Code + twofa_hint_disabled_html: Die Einstellung %{label} deaktiviert Zwei-Faktor-Authentifizierung für alle Nutzer und löscht verbundene Apps. + twofa_hint_required_html: Die Einstellung %{label} fordert alle Nutzer bei ihrem nächsten Login dazu auf Zwei-Faktor-Authentifizierung einzurichten. twofa_label_setup: Zwei-Faktor-Authentifizierung einrichten twofa_label_deactivation_confirmation: Zwei-Faktor-Authentifizierung abschalten + twofa_notice_select: "Bitte wählen Sie Ihr gewünschtes Schema für die Zwei-Faktor-Authentifizierung:" + twofa_warning_require: Der Administrator fordert Sie dazu auf Zwei-Faktor-Authentifizierung einzurichten. twofa_activated: Zwei-Faktor-Authentifizierung erfolgreich eingerichtet. twofa_deactivated: Zwei-Faktor-Authentifizierung abgeschaltet. twofa_mail_body_security_notification_paired: "Zwei-Faktor-Authentifizierung per %{field} eingerichtet." diff --git a/config/locales/en.yml b/config/locales/en.yml index cc820eabd..ee2196501 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -870,6 +870,7 @@ en: label_copied_from: Copied from label_stay_logged_in: Stay logged in label_disabled: disabled + label_optional: optional label_show_completed_versions: Show completed versions label_me: me label_board: Forum @@ -993,6 +994,7 @@ en: label_fields_permissions: Fields permissions label_readonly: Read-only label_required: Required + label_required_lower: required label_hidden: Hidden label_attribute_of_project: "Project's %{name}" label_attribute_of_issue: "Issue's %{name}" @@ -1307,8 +1309,12 @@ en: twofa_currently_active: "Currently active: %{twofa_scheme_name}" twofa_not_active: "Not activated" twofa_label_code: Code + twofa_hint_disabled_html: Setting %{label} will deactivate and unpair two-factor authentication devices for all users. + twofa_hint_required_html: Setting %{label} will require all users to set up two-factor authentication at their next login. twofa_label_setup: Enable two-factor authentication twofa_label_deactivation_confirmation: Disable two-factor authentication + twofa_notice_select: "Please select the two-factor scheme you would like to use:" + twofa_warning_require: The administrator requires you to enable two-factor authentication. twofa_activated: Two-factor authentication successfully enabled. twofa_deactivated: Two-factor authentication disabled. twofa_mail_body_security_notification_paired: "Two-factor authentication successfully enabled using %{field}." diff --git a/config/routes.rb b/config/routes.rb index 3e6ae9cc7..97ecf2913 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,12 +88,14 @@ Rails.application.routes.draw do match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post + match 'my/twofa/activate/init', :controller => 'twofa', :action => 'activate_init', :via => :post match 'my/twofa/:scheme/activate/init', :controller => 'twofa', :action => 'activate_init', :via => :post match 'my/twofa/:scheme/activate/confirm', :controller => 'twofa', :action => 'activate_confirm', :via => :get match 'my/twofa/:scheme/activate', :controller => 'twofa', :action => 'activate', :via => [:get, :post] match 'my/twofa/:scheme/deactivate/init', :controller => 'twofa', :action => 'deactivate_init', :via => :post match 'my/twofa/:scheme/deactivate/confirm', :controller => 'twofa', :action => 'deactivate_confirm', :via => :get match 'my/twofa/:scheme/deactivate', :controller => 'twofa', :action => 'deactivate', :via => [:get, :post] + match 'my/twofa/select_scheme', :controller => 'twofa', :action => 'select_scheme', :via => :get match 'users/:user_id/twofa/deactivate', :controller => 'twofa', :action => 'admin_deactivate', :via => :post resources :users do diff --git a/config/settings.yml b/config/settings.yml index 7c0912232..01f55a970 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -34,6 +34,9 @@ show_custom_fields_on_registration: lost_password: default: 1 security_notifications: 1 +twofa: + default: 1 + security_notifications: 1 unsubscribe: default: 1 password_required_char_classes: diff --git a/lib/redmine/twofa.rb b/lib/redmine/twofa.rb index 44f1b7ac6..0096f3951 100644 --- a/lib/redmine/twofa.rb +++ b/lib/redmine/twofa.rb @@ -36,6 +36,11 @@ module Redmine for_twofa_scheme(user.twofa_scheme).try(:new, user) end + def self.unpair_all! + users = User.where.not(twofa_scheme: nil) + users.each { |u| self.for_user(u).destroy_pairing_without_verify! } + end + def self.schemes initialize_schemes @@schemes -- 2.26.2