diff options
29 files changed, 309 insertions, 65 deletions
diff --git a/CHANGELOG b/CHANGELOG index 29a175bea80..78d717a709f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,20 +1,28 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) - - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add API support for looking up a user by username (Stan Hu) - Add project permissions to all project API endpoints (Stan Hu) + - Expose Git's version in the admin area + - Add "Frequently used" category to emoji picker + - Add CAS support (tduehr) + - Add link to merge request on build detail page. + +v 8.3.2 (unreleased) + - Enable "Add key" button when user fills in a proper key -v 8.3.1 (unreleased) +v 8.3.1 - Fix Error 500 when global milestones have slashes (Stan Hu) + - 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 - - Add CAS support (tduehr) - Bump rack-attack to 4.3.1 for security fix (Stan Hu) - API support for starred projects for authorized user (Zeger-Jan van de Weg) - - Add link to merge request on build detail page. - Add open_issues_count to project API (Stan Hu) - Expand character set of usernames created by Omniauth (Corey Hinshaw) - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) @@ -74,7 +82,6 @@ v 8.3.0 - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - Persist runners registration token in database - Fix online editor should not remove newlines at the end of the file - - Expose Git's version in the admin area v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 392440a2b00..04bf5cc7bb5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -10,6 +10,7 @@ class @AwardsHandler if $(".emoji-menu").is(":visible") $(".emoji-menu").hide() + @renderFrequentlyUsedBlock() @setupSearch() addAward: (emoji) -> @@ -20,6 +21,8 @@ class @AwardsHandler $(".emoji-menu").hide() addAwardToEmojiBar: (emoji) -> + @addEmojiToFrequentlyUsedList(emoji) + emoji = @normilizeEmojiName(emoji) if @exist(emoji) if @isActive(emoji) @@ -117,6 +120,29 @@ class @AwardsHandler normilizeEmojiName: (emoji) -> @aliases[emoji] || emoji + addEmojiToFrequentlyUsedList: (emoji) -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + frequently_used_emojis.push(emoji) + $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + + getFrequentlyUsedEmojis: -> + frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") + + frequently_used_emojis = ["thumbsup", "thumbsdown"].concat(frequently_used_emojis) + + _.compact(_.uniq(frequently_used_emojis)) + + renderFrequentlyUsedBlock: -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + + ul = $("<ul>") + + for emoji in frequently_used_emojis + do (emoji) -> + $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) + + $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used")) + setupSearch: -> $("input.emoji-search").keyup (ev) => term = $(ev.target).val() @@ -125,7 +151,7 @@ class @AwardsHandler $("ul.emoji-search,h5.emoji-search").remove() if term - # Generate search result block + # Generate a search result block h5 = $("<h5>").text("Search results").addClass("emoji-search") found_emojis = @searchEmojis(term).show() ul = $("<ul>").addClass("emoji-search").append(found_emojis) 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/ability.rb b/app/models/ability.rb index cd5ae0fb0fd..1b3ee757040 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -132,14 +132,14 @@ class Ability end def public_project_rules - project_guest_rules + [ + @public_project_rules ||= project_guest_rules + [ :download_code, :fork_project ] end def project_guest_rules - [ + @project_guest_rules ||= [ :read_project, :read_wiki, :read_issue, @@ -157,7 +157,7 @@ class Ability end def project_report_rules - project_guest_rules + [ + @project_report_rules ||= project_guest_rules + [ :create_commit_status, :read_commit_statuses, :download_code, @@ -170,7 +170,7 @@ class Ability end def project_dev_rules - project_report_rules + [ + @project_dev_rules ||= project_report_rules + [ :admin_merge_request, :create_merge_request, :create_wiki, @@ -181,7 +181,7 @@ class Ability end def project_archived_rules - [ + @project_archived_rules ||= [ :create_merge_request, :push_code, :push_code_to_protected_branches, @@ -191,7 +191,7 @@ class Ability end def project_master_rules - project_dev_rules + [ + @project_master_rules ||= project_dev_rules + [ :push_code_to_protected_branches, :update_project_snippet, :update_merge_request, @@ -206,7 +206,7 @@ class Ability end def project_admin_rules - project_master_rules + [ + @project_admin_rules ||= project_master_rules + [ :change_namespace, :change_visibility_level, :rename_project, @@ -332,7 +332,7 @@ class Ability end if snippet.public? || snippet.internal? - rules << :read_personal_snippet + rules << :read_personal_snippet end rules 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/models/identity.rb b/app/models/identity.rb index ad60154be71..8bcdc194953 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -12,6 +12,7 @@ class Identity < ActiveRecord::Base include Sortable + include CaseSensitivity belongs_to :user validates :provider, presence: true 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/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index 11166dc6d99..13a18269d11 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -12,6 +12,6 @@ comment = val.match(/^\S+ \S+ (.+)\n?$/); if( comment && comment.length > 1 && title.val() == '' ){ - $('#key_title').val( comment[1] ); + $('#key_title').val( comment[1] ).change(); } }); 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/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index 2126e826ddc..9a06fdc2ee6 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -26,5 +26,5 @@ Feature: Award Emoji @javascript Scenario: I add award emoji using regular comment - Given I leave comment with a single emoji - Then I have award added + Given I leave comment with a single emoji + Then I have award added diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index bdaa4721b4b..63ad8910c0f 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -98,7 +98,7 @@ module Banzai project = project_from_ref(project_ref) if project && object = find_object(project, id) - title = escape_once(object_link_title(object)) + title = object_link_title(object) klass = reference_class(object_sym) data = data_attribute( @@ -110,17 +110,11 @@ module Banzai url = matches[:url] if matches.names.include?("url") url ||= url_for_object(object, project) - text = link_text - unless text - text = object.reference_link_text(context[:project]) - - extras = object_link_text_extras(object, matches) - text += " (#{extras.join(", ")})" if extras.any? - end + text = link_text || object_link_text(object, matches) %(<a href="#{url}" #{data} - title="#{title}" - class="#{klass}">#{text}</a>) + title="#{escape_once(title)}" + class="#{klass}">#{escape_once(text)}</a>) else match end @@ -140,6 +134,15 @@ module Banzai def object_link_title(object) "#{object_class.name.titleize}: #{object.title}" end + + def object_link_text(object, matches) + text = object.reference_link_text(context[:project]) + + extras = object_link_text_extras(object, matches) + text += " (#{extras.join(", ")})" if extras.any? + + text + end end end end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index f5942740cd6..6136e73c096 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -63,15 +63,15 @@ module Banzai url = url_for_issue(id, project, only_path: context[:only_path]) - title = escape_once("Issue in #{project.external_issue_tracker.title}") + title = "Issue in #{project.external_issue_tracker.title}" klass = reference_class(:issue) data = data_attribute(project: project.id, external_issue: id) text = link_text || match %(<a href="#{url}" #{data} - title="#{title}" - class="#{klass}">#{text}</a>) + title="#{escape_once(title)}" + class="#{klass}">#{escape_once(text)}</a>) end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 07bac2dd7fd..a3a7a23c1e6 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -60,7 +60,7 @@ module Banzai text = link_text || render_colored_label(label) %(<a href="#{url}" #{data} - class="#{klass}">#{text}</a>) + class="#{klass}">#{escape_once(text)}</a>) else match end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 33457a3f361..a22a7a7afd3 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -44,11 +44,11 @@ module Banzai # Returns a String def data_attribute(attributes = {}) attributes[:reference_filter] = self.class.name.demodulize - attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ") + attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ") end def escape_once(html) - ERB::Util.html_escape_once(html) + html.html_safe? ? html : ERB::Util.html_escape_once(html) end def ignore_parents diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index 67c24faf991..7f302d51dd7 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -122,7 +122,7 @@ module Banzai end def link_tag(url, data, text) - %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>) + %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>) end end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 4be99dd88c2..aef08c97d1d 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -14,7 +14,7 @@ module Gitlab # LDAP distinguished name is case-insensitive identity = ::Identity. where(provider: provider). - where('lower(extern_uid) = ?', uid.mb_chars.downcase.to_s).last + iwhere(extern_uid: uid).last identity && identity.user end end @@ -31,7 +31,7 @@ module Gitlab def find_by_uid_and_provider self.class.find_by_uid_and_provider( - auth_hash.uid.downcase, auth_hash.provider) + auth_hash.uid, auth_hash.provider) end def find_by_email @@ -47,7 +47,7 @@ module Gitlab # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved. identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider } identity ||= gl_user.identities.build(provider: auth_hash.provider) - + # For a new user set extern_uid to the LDAP DN # For an existing user with matching email but changed DN, update the DN. # For an existing user with no change in DN, this line changes nothing. diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 17ce4d4b174..f1a362f5303 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -64,7 +64,7 @@ module Gitlab # If a corresponding person exists with same uid in a LDAP server, # set up a Gitlab user with dual LDAP and Omniauth identities. - if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider) + if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider) # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account. user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) else 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/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 3bba5e2efa2..1e755259dae 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -42,6 +42,21 @@ describe Gitlab::LDAP::User, lib: true do end end + describe '.find_by_uid_and_provider' do + it 'retrieves the correct user' do + special_info = { + name: 'John Åström', + email: 'john@example.com', + nickname: 'jastrom' + } + special_hash = OmniAuth::AuthHash.new(uid: 'CN=John Åström,CN=Users,DC=Example,DC=com', provider: 'ldapmain', info: special_info) + special_chars_user = described_class.new(special_hash) + user = special_chars_user.save + + expect(described_class.find_by_uid_and_provider(special_hash.uid, special_hash.provider)).to eq user + end + end + describe :find_or_create do it "finds the user if already existing" do create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') 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' |