From afa1980639b27fd476d9ce5502be095d3f99cb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=A4fer?= Date: Wed, 15 Nov 2017 00:52:47 +0100 Subject: [PATCH 2/3] 2-factor authentication disabled/enabled/required --- 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 | 10 ++++++++++ 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, 108 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 a05f54077..06cec12d5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -51,7 +51,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 rescue_from ::Unauthorized, :with => :deny_access rescue_from ::ActionView::MissingTemplate, :with => :missing_template @@ -83,6 +83,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 @@ -194,6 +197,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 8a64a05ae..b18157735 100644 --- a/app/controllers/twofa_controller.rb +++ b/app/controllers/twofa_controller.rb @@ -4,16 +4,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 @@ -84,4 +88,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 b73aae4f2..e77f39ada 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -198,6 +198,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 15687b608..8aaa4b179 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -373,6 +373,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 8ffaecf45..f12ce5eb3 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -27,6 +27,7 @@ <% if Setting.openid? %>

<%= f.text_field :identity_url %>

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

<% if @user.twofa_active? %> @@ -38,6 +39,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 4454bbae4..bff1e6290 100644 --- a/app/views/settings/_authentication.html.erb +++ b/app/views/settings/_authentication.html.erb @@ -25,6 +25,16 @@

<%= 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_text_field :max_additional_emails, :size => 6 %>

<%= 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 c86586b5f..a38a3eb3f 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -36,6 +36,7 @@

<%= f.check_box :generate_password %>

<%= f.check_box :must_change_passwd %>

+ <% if Setting.twofa? -%>

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

+ <% end -%> diff --git a/config/locales/de.yml b/config/locales/de.yml index b8d7436c6..f377e18fa 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -713,6 +713,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 @@ -1244,8 +1245,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 264b1e9cf..5337107f4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -836,6 +836,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 @@ -953,6 +954,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}" @@ -1225,8 +1227,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 3c2a2fa21..c33800a76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -85,12 +85,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 c10ae1acc..9d1e3fab1 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -36,6 +36,9 @@ show_custom_fields_on_registration: lost_password: default: 1 security_notifications: 1 +twofa: + default: 1 + security_notifications: 1 unsubscribe: default: 1 password_min_length: diff --git a/lib/redmine/twofa.rb b/lib/redmine/twofa.rb index 9e9a3e1df..0306fa466 100644 --- a/lib/redmine/twofa.rb +++ b/lib/redmine/twofa.rb @@ -17,6 +17,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 + private def self.schemes -- 2.15.1