Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-12-25 12:27:02 +0300
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-12-25 12:27:02 +0300
commitb1b5b2218fc7dc9b26c6375163f3a9fbd32f85c5 (patch)
treeb19e5b7a7c47f3097aa1e629c19904fc0d192ff1
parent350d65503f0fa34ae397942578c5ea8b2a46a629 (diff)
parent1249289f89feba725109ce769e685b07cf746e4b (diff)
Merge branch 'feature/force-tfa' into 'master'
Require two factor authentication Allow Admins to force 2FA on their users, as discussed here: gitlab-org/gitlab-ce#3783 See merge request !2155
-rw-r--r--CHANGELOG1
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/application_controller.rb24
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb25
-rw-r--r--app/helpers/auth_helper.rb12
-rw-r--r--app/models/application_setting.rb59
-rw-r--r--app/views/admin/application_settings/_form.html.haml12
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml1
-rw-r--r--config/routes.rb1
-rw-r--r--db/migrate/20151218154042_add_tfa_to_application_settings.rb8
-rw-r--r--db/migrate/20151221234414_add_tfa_additional_fields.rb7
-rw-r--r--db/schema.rb3
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/two_factor_authentication.md38
-rw-r--r--spec/features/login_spec.rb52
-rw-r--r--spec/models/application_setting_spec.rb1
16 files changed, 220 insertions, 27 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 2e12889cb70..4591dc3013d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@ v 8.3.1
- Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu)
- Fix LDAP identity and user retrieval when special characters are used
- Move Sidekiq-cron configuration to gitlab.yml
+ - Enable forcing Two-Factor authentication sitewide, with optional grace period
v 8.3.0
- Bump rack-attack to 4.3.1 for security fix (Stan Hu)
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9dd16f8c735..2f4a855c118 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -49,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_branch_protection,
:signup_enabled,
:signin_enabled,
+ :require_two_factor_authentication,
+ :two_factor_grace_period,
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 01e2e7b2f98..d9a37a4d45f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
before_action :validate_user_service_ticket!
before_action :reject_blocked!
before_action :check_password_expiration
+ before_action :check_2fa_requirement
before_action :ldap_security_check
before_action :default_headers
before_action :add_gon_variables
@@ -223,6 +224,12 @@ class ApplicationController < ActionController::Base
end
end
+ def check_2fa_requirement
+ if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
+ redirect_to new_profile_two_factor_auth_path
+ end
+ end
+
def ldap_security_check
if current_user && current_user.requires_ldap_check?
unless Gitlab::LDAP::Access.allowed?(current_user)
@@ -357,6 +364,23 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git')
end
+ def two_factor_authentication_required?
+ current_application_settings.require_two_factor_authentication
+ end
+
+ def two_factor_grace_period
+ current_application_settings.two_factor_grace_period
+ end
+
+ def two_factor_grace_period_expired?
+ date = current_user.otp_grace_period_started_at
+ date && (date + two_factor_grace_period.hours) < Time.current
+ end
+
+ def skip_two_factor?
+ session[:skip_tfa] && session[:skip_tfa] > Time.current
+ end
+
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e6b99be37fb..6e91d9b4ad9 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -1,8 +1,22 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
+ skip_before_action :check_2fa_requirement
+
def new
unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32)
- current_user.save!
+ end
+
+ unless current_user.otp_grace_period_started_at && two_factor_grace_period
+ current_user.otp_grace_period_started_at = Time.current
+ end
+
+ current_user.save! if current_user.changed?
+
+ if two_factor_grace_period_expired?
+ flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
+ else
+ grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+ flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
end
@qr_code = build_qr_code
@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
redirect_to profile_account_path
end
+ def skip
+ if two_factor_grace_period_expired?
+ redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
+ else
+ session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+ redirect_to root_path
+ end
+ end
+
private
def build_qr_code
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2c81ea1623c..0cfc0565e84 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -50,5 +50,17 @@ module AuthHelper
current_user.identities.exists?(provider: provider.to_s)
end
+ def two_factor_skippable?
+ current_application_settings.require_two_factor_authentication &&
+ !current_user.two_factor_enabled &&
+ current_application_settings.two_factor_grace_period &&
+ !two_factor_grace_period_expired?
+ end
+
+ def two_factor_grace_period_expired?
+ current_user.otp_grace_period_started_at &&
+ (current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
+ end
+
extend self
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 724429e7558..7c107da116c 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -2,32 +2,34 @@
#
# Table name: application_settings
#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string(255)
+# id :integer not null, primary key
+# default_projects_limit :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
+# max_attachment_size :integer default(10), not null
+# default_project_visibility :integer
+# default_snippet_visibility :integer
+# restricted_signup_domains :text
+# user_oauth_applications :boolean default(TRUE)
+# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
+# import_sources :text
+# help_page_text :text
+# admin_notification_email :string(255)
+# shared_runners_enabled :boolean default(TRUE), not null
+# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string(255)
+# require_two_factor_authentication :boolean default(TRUE)
+# two_factor_grace_period :integer default(48)
#
class ApplicationSetting < ActiveRecord::Base
@@ -58,6 +60,9 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
email: true
+ validates :two_factor_grace_period,
+ numericality: { greater_than_or_equal_to: 0 }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -112,6 +117,8 @@ class ApplicationSetting < ActiveRecord::Base
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48
)
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 6c355366948..58f5c621f4a 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -105,6 +105,18 @@
= f.check_box :signin_enabled
Sign-in enabled
.form-group
+ = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+ .col-sm-10
+ .checkbox
+ = f.label :require_two_factor_authentication do
+ = f.check_box :require_two_factor_authentication
+ Require all users to setup Two-Factor authentication
+ .form-group
+ = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
+ .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
+ .form-group
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 92dc58c10d7..1a5b6efce35 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -38,3 +38,4 @@
= text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
.form-actions
= submit_tag 'Submit', class: 'btn btn-success'
+ = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch, class: 'btn btn-cancel' if two_factor_skippable?
diff --git a/config/routes.rb b/config/routes.rb
index b9242327de1..3e7d9f78710 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -297,6 +297,7 @@ Rails.application.routes.draw do
resource :two_factor_auth, only: [:new, :create, :destroy] do
member do
post :codes
+ patch :skip
end
end
end
diff --git a/db/migrate/20151218154042_add_tfa_to_application_settings.rb b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
new file mode 100644
index 00000000000..dd95db775c5
--- /dev/null
+++ b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddTfaToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :require_two_factor_authentication, default: false
+ t.integer :two_factor_grace_period, default: 48
+ end
+ end
+end
diff --git a/db/migrate/20151221234414_add_tfa_additional_fields.rb b/db/migrate/20151221234414_add_tfa_additional_fields.rb
new file mode 100644
index 00000000000..c16df47932f
--- /dev/null
+++ b/db/migrate/20151221234414_add_tfa_additional_fields.rb
@@ -0,0 +1,7 @@
+class AddTfaAdditionalFields < ActiveRecord::Migration
+ def change
+ change_table :users do |t|
+ t.datetime :otp_grace_period_started_at, null: true
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0d53105b057..49fa258660d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -50,6 +50,8 @@ ActiveRecord::Schema.define(version: 20151224123230) do
t.boolean "shared_runners_enabled", default: true, null: false
t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
+ t.boolean "require_two_factor_authentication", default: false
+ t.integer "two_factor_grace_period", default: 48
end
create_table "audit_events", force: :cascade do |t|
@@ -838,6 +840,7 @@ ActiveRecord::Schema.define(version: 20151224123230) do
t.integer "layout", default: 0
t.boolean "hide_project_limit", default: false
t.string "unlock_token"
+ t.datetime "otp_grace_period_started_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/security/README.md b/doc/security/README.md
index fba6013d9c1..384df570394 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -6,3 +6,4 @@
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
+- [Enforce Two-Factor authentication](two_factor_authentication.md)
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
new file mode 100644
index 00000000000..4e25a1fdc3f
--- /dev/null
+++ b/doc/security/two_factor_authentication.md
@@ -0,0 +1,38 @@
+# Enforce Two-factor Authentication (2FA)
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+users' GitLab account. Once enabled, in addition to supplying their username and
+password to login, they'll be prompted for a code generated by an application on
+their phone.
+
+You can read more about it here:
+[Two-factor Authentication (2FA)](doc/profile/two_factor_authentication.md)
+
+## Enabling 2FA
+
+Users on GitLab, can enable it without any admin's intervention. If you want to
+enforce everyone to setup 2FA, you can choose from two different ways:
+
+ 1. Enforce on next login
+ 2. Suggest on next login, but allow a grace period before enforcing.
+
+In the Admin area under **Settings** (`/admin/application_settings`), look for
+the "Sign-in Restrictions" area, where you can configure both.
+
+If you want 2FA enforcement to take effect on next login, change the grace
+period to `0`
+
+## Disabling 2FA for everyone
+
+There may be some special situations where you want to disable 2FA for everyone
+even when forced 2FA is disabled. There is a rake task for that:
+
+```
+# use this command if you've installed GitLab with the Omnibus package
+sudo gitlab-rake gitlab:two_factor:disable_for_all_users
+
+# if you've installed GitLab from source
+sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
+```
+
+**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.**
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 922c76285d1..2451e56fe7c 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -98,4 +98,56 @@ feature 'Login', feature: true do
expect(page).to have_content('Invalid login or password.')
end
end
+
+ describe 'with required two-factor authentication enabled' do
+ let(:user) { create(:user) }
+ before(:each) { stub_application_setting(require_two_factor_authentication: true) }
+
+ context 'with grace period defined' do
+ before(:each) do
+ stub_application_setting(two_factor_grace_period: 48)
+ login_with(user)
+ end
+
+ context 'within the grace period' do
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+ end
+
+ it 'two-factor configuration is skippable' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+
+ click_link 'Configure it later'
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'after the grace period' do
+ let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
+
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ end
+
+ it 'two-factor configuration is not skippable' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).not_to have_link('Configure it later')
+ end
+ end
+ end
+
+ context 'without grace pariod defined' do
+ before(:each) do
+ stub_application_setting(two_factor_grace_period: 0)
+ login_with(user)
+ end
+
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ end
+ end
+ end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 5f64453a35f..35d8220ae54 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -27,6 +27,7 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string(255)
#
require 'spec_helper'