]> source.dussan.org Git - redmine.git/commitdiff
Adds a setting to disable/enable/require 2fa auth (#1237).
authorGo MAEDA <maeda@farend.jp>
Sat, 29 Aug 2020 06:37:09 +0000 (06:37 +0000)
committerGo MAEDA <maeda@farend.jp>
Sat, 29 Aug 2020 06:37:09 +0000 (06:37 +0000)
Patch by Felix Schäfer.

git-svn-id: http://svn.redmine.org/redmine/trunk@19989 e93f8b46-1217-0410-a6f0-8f06a7374b81

14 files changed:
app/controllers/application_controller.rb
app/controllers/twofa_controller.rb
app/models/setting.rb
app/models/user.rb
app/views/my/account.html.erb
app/views/settings/_authentication.html.erb
app/views/twofa/activate_confirm.html.erb
app/views/twofa/select_scheme.html.erb [new file with mode: 0644]
app/views/users/_form.html.erb
config/locales/de.yml
config/locales/en.yml
config/routes.rb
config/settings.yml
lib/redmine/twofa.rb

index fca9ebc90265cacd2d4545722e25a9fd5a4664e4..08dea30a784e74d6b6a7de326bd667010a12c352 100644 (file)
@@ -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?
index 4cdeeee4f597fcdb4ca14c7d3dc8e3258a235418..8bbdb80566c730ed93a1f2e27731cbca2cd11d03 100644 (file)
@@ -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
@@ -106,4 +110,8 @@ class TwofaController < ApplicationController
       redirect_to my_account_path
     end
   end
+
+  def require_active_twofa
+    Setting.twofa? ? true : deny_access
+  end
 end
index da121263db8e133d8a5f19832b6c29dfa502e058..e71a9a0cfb3d53462668b6264e941af474a03d79 100644 (file)
@@ -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
index 5b4089ea464bf88d6f6e2f5ce27bf3bc6e5cb7e9..d905aa1b31d0223e46e050524e22aa542588d6c1 100644 (file)
@@ -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
index da7746bb2b342a641be4983b6fb6fa361051498e..996bead61705c21658d3cbbb25490ba11bf3c0a3 100644 (file)
@@ -28,6 +28,7 @@
   <% if Setting.openid? %>
   <p><%= f.text_field :identity_url  %></p>
   <% end %>
+  <% if Setting.twofa? -%>
   <p>
     <label><%=l :setting_twofa -%></label>
     <% if @user.twofa_active? %>
@@ -39,6 +40,7 @@
       <% end %>
     <% end %>
   </p>
+  <% end -%>
 
   <% @user.custom_field_values.select(&:editable?).each do |value| %>
     <p><%= custom_field_tag_with_label :user, value %></p>
index 9a39497b8027f397919ac5897e118df9be6efea0..5522ff5cff1e10c57d6a968d5db75730840c4f72 100644 (file)
 
 <p><%= setting_check_box :lost_password %></p>
 
+<p>
+  <%= setting_select :twofa, [[l(:label_disabled), "0"],
+                              [l(:label_optional), "1"],
+                              [l(:label_required_lower), "2"]] -%>
+  <em class="info">
+    <%= t 'twofa_hint_disabled_html', label: t(:label_disabled) -%><br/>
+    <%= t 'twofa_hint_required_html', label: t(:label_required_lower) -%>
+  </em>
+</p>
+
+
 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
 </div>
 
index fc356323c2a3cf498980bb1d58b0438abdce1fa9..78194c62b07f844bef5dff698175f45b73b609d5 100644 (file)
@@ -22,6 +22,8 @@
   <% end %>
 </div>
 
+<% 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 (file)
index 0000000..cbc7c35
--- /dev/null
@@ -0,0 +1,19 @@
+<%= title l('twofa_label_setup') %>
+
+<%= form_tag({ controller: 'twofa', action: 'activate_init' }, method: :post) do %>
+  <div class="box">
+  <p><%=l 'twofa_notice_select' -%></p>
+  <p>
+  <% Redmine::Twofa.available_schemes.each_with_index do |s, idx| %>
+    <label style="display:block;"><%= radio_button_tag 'scheme', s, idx == 0 -%> <%=l "twofa__#{s}__name" -%></label>
+  <% end %>
+  </p>
+  </div>
+  <p><%= submit_tag l(:label_next).html_safe + " &#187;".html_safe -%></p>
+<% end %>
+
+<% unless @user.must_activate_twofa? %>
+<% content_for :sidebar do %>
+<%= render partial: 'my/sidebar' %>
+<% end %>
+<% end %>
index b9054a3b035950cec332770eca5639562ab7ade8..ab9f7b5416dfb646acaec68c51283065260338bc 100644 (file)
@@ -42,6 +42,7 @@
   <p><%= f.check_box :generate_password %></p>
   <p><%= f.check_box :must_change_passwd %></p>
   </div>
+  <% if Setting.twofa? -%>
   <p>
     <label><%=l :setting_twofa -%></label>
     <% if @user.twofa_active? %>
@@ -55,6 +56,7 @@
       <%=l 'twofa_not_active' %>
     <% end %>
   </p>
+  <% end -%>
 </fieldset>
 </div>
 
index c86f39533ce65545d28656ff0313dd0db0e16d23..b588ac2ebd53bd2f9e853cb959f4821dee3c5a06 100644 (file)
@@ -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 <strong>%{label}</strong> deaktiviert Zwei-Faktor-Authentifizierung für alle Nutzer und löscht verbundene Apps.
+  twofa_hint_required_html: Die Einstellung <strong>%{label}</strong> 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."
index cc820eabd0678ffa1e23afc42c77eaaae743f3cc..ee21965016bef36771b955d08ad3d57ce85f2d32 100644 (file)
@@ -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 <strong>%{label}</strong> will deactivate and unpair two-factor authentication devices for all users.
+  twofa_hint_required_html: Setting <strong>%{label}</strong> 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}."
index 3e6ae9cc77550095ed1d5387b4b2c4767abc05af..97ecf2913ef160a6bfd3424c68a35795f5caa5a0 100644 (file)
@@ -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
index 7c0912232d9ee0f4db20927fea8959a6322da77c..01f55a9700b8671214436d17fd8210d7b56b51fb 100644 (file)
@@ -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:
index 44f1b7ac61a563b4921f83a10d8303e5c7f41757..0096f395135c1ad0cccc54c010d1b1baba70fadb 100644 (file)
@@ -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