Merge branch 'security-59549-add-capcha-for-failed-logins' into 'master'
Require a captcha after unique failed logins from the same IP See merge request gitlab/gitlabhq!3270
This commit is contained in:
commit
a5b2a37860
20 changed files with 307 additions and 32 deletions
|
@ -21,10 +21,13 @@ class SessionsController < Devise::SessionsController
|
|||
prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? }
|
||||
|
||||
before_action :auto_sign_in_with_provider, only: [:new]
|
||||
before_action :store_unauthenticated_sessions, only: [:new]
|
||||
before_action :save_failed_login, if: :action_new_and_failed_login?
|
||||
before_action :load_recaptcha
|
||||
|
||||
after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
|
||||
helper_method :captcha_enabled?
|
||||
after_action :log_failed_login, if: :action_new_and_failed_login?
|
||||
|
||||
helper_method :captcha_enabled?, :captcha_on_login_required?
|
||||
|
||||
# protect_from_forgery is already prepended in ApplicationController but
|
||||
# authenticate_with_two_factor which signs in the user is prepended before
|
||||
|
@ -38,6 +41,7 @@ class SessionsController < Devise::SessionsController
|
|||
protect_from_forgery with: :exception, prepend: true
|
||||
|
||||
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
|
||||
MAX_FAILED_LOGIN_ATTEMPTS = 5
|
||||
|
||||
def new
|
||||
set_minimum_password_length
|
||||
|
@ -81,10 +85,14 @@ class SessionsController < Devise::SessionsController
|
|||
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
|
||||
end
|
||||
|
||||
def captcha_on_login_required?
|
||||
Gitlab::Recaptcha.enabled_on_login? && unverified_anonymous_user?
|
||||
end
|
||||
|
||||
# From https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise#devisepasswordscontroller
|
||||
def check_captcha
|
||||
return unless user_params[:password].present?
|
||||
return unless captcha_enabled?
|
||||
return unless captcha_enabled? || captcha_on_login_required?
|
||||
return unless Gitlab::Recaptcha.load_configurations!
|
||||
|
||||
if verify_recaptcha
|
||||
|
@ -126,10 +134,28 @@ class SessionsController < Devise::SessionsController
|
|||
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
|
||||
end
|
||||
|
||||
def action_new_and_failed_login?
|
||||
action_name == 'new' && failed_login?
|
||||
end
|
||||
|
||||
def save_failed_login
|
||||
session[:failed_login_attempts] ||= 0
|
||||
session[:failed_login_attempts] += 1
|
||||
end
|
||||
|
||||
def failed_login?
|
||||
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
|
||||
end
|
||||
|
||||
# storing sessions per IP lets us check if there are associated multiple
|
||||
# anonymous sessions with one IP and prevent situations when there are
|
||||
# multiple attempts of logging in
|
||||
def store_unauthenticated_sessions
|
||||
return if current_user
|
||||
|
||||
Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
|
||||
end
|
||||
|
||||
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
||||
# and they require a password change.
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
@ -240,6 +266,18 @@ class SessionsController < Devise::SessionsController
|
|||
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
|
||||
end
|
||||
|
||||
def unverified_anonymous_user?
|
||||
exceeded_failed_login_attempts? || exceeded_anonymous_sessions?
|
||||
end
|
||||
|
||||
def exceeded_failed_login_attempts?
|
||||
session.fetch(:failed_login_attempts, 0) > MAX_FAILED_LOGIN_ATTEMPTS
|
||||
end
|
||||
|
||||
def exceeded_anonymous_sessions?
|
||||
Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
|
||||
end
|
||||
|
||||
def authentication_method
|
||||
if user_params[:otp_attempt]
|
||||
"two-factor"
|
||||
|
|
|
@ -231,6 +231,7 @@ module ApplicationSettingsHelper
|
|||
:recaptcha_enabled,
|
||||
:recaptcha_private_key,
|
||||
:recaptcha_site_key,
|
||||
:login_recaptcha_protection_enabled,
|
||||
:receive_max_input_size,
|
||||
:repository_checks_enabled,
|
||||
:repository_storages,
|
||||
|
|
|
@ -75,11 +75,11 @@ class ApplicationSetting < ApplicationRecord
|
|||
|
||||
validates :recaptcha_site_key,
|
||||
presence: true,
|
||||
if: :recaptcha_enabled
|
||||
if: :recaptcha_or_login_protection_enabled
|
||||
|
||||
validates :recaptcha_private_key,
|
||||
presence: true,
|
||||
if: :recaptcha_enabled
|
||||
if: :recaptcha_or_login_protection_enabled
|
||||
|
||||
validates :akismet_api_key,
|
||||
presence: true,
|
||||
|
@ -292,4 +292,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
def self.cache_backend
|
||||
Gitlab::ThreadMemoryCache.cache_backend
|
||||
end
|
||||
|
||||
def recaptcha_or_login_protection_enabled
|
||||
recaptcha_enabled || login_recaptcha_protection_enabled
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,6 +64,7 @@ module ApplicationSettingImplementation
|
|||
polling_interval_multiplier: 1,
|
||||
project_export_enabled: true,
|
||||
recaptcha_enabled: false,
|
||||
login_recaptcha_protection_enabled: false,
|
||||
repository_checks_enabled: true,
|
||||
repository_storages: ['default'],
|
||||
require_two_factor_authentication: false,
|
||||
|
|
|
@ -7,11 +7,15 @@
|
|||
= f.check_box :recaptcha_enabled, class: 'form-check-input'
|
||||
= f.label :recaptcha_enabled, class: 'form-check-label' do
|
||||
Enable reCAPTCHA
|
||||
- recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
|
||||
- recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
|
||||
%span.form-text.text-muted#recaptcha_help_block
|
||||
= _('Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
|
||||
|
||||
= _('Helps prevent bots from creating accounts.')
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :login_recaptcha_protection_enabled, class: 'form-check-input'
|
||||
= f.label :login_recaptcha_protection_enabled, class: 'form-check-label' do
|
||||
Enable reCAPTCHA for login
|
||||
%span.form-text.text-muted#recaptcha_help_block
|
||||
= _('Helps prevent bots from brute-force attacks.')
|
||||
.form-group
|
||||
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'label-bold'
|
||||
= f.text_field :recaptcha_site_key, class: 'form-control'
|
||||
|
@ -21,6 +25,7 @@
|
|||
|
||||
.form-group
|
||||
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'label-bold'
|
||||
.form-group
|
||||
= f.text_field :recaptcha_private_key, class: 'form-control'
|
||||
|
||||
.form-group
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Enable reCAPTCHA or Akismet and set IP limits.')
|
||||
- recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
|
||||
- recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
|
||||
= _('Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
|
||||
.settings-content
|
||||
= render 'spam'
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
- else
|
||||
= link_to _('Forgot your password?'), new_password_path(:user)
|
||||
%div
|
||||
- if captcha_enabled?
|
||||
- if captcha_enabled? || captcha_on_login_required?
|
||||
= recaptcha_tags
|
||||
|
||||
.submit-container.move-submit-down
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -19,6 +19,7 @@ Rails.application.configure do |config|
|
|||
|
||||
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
|
||||
ActiveSession.cleanup(user)
|
||||
Gitlab::AnonymousSession.new(auth.request.remote_ip, session_id: auth.request.session.id).cleanup_session_per_ip_entries
|
||||
end
|
||||
|
||||
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddLoginRecaptchaProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :application_settings, :login_recaptcha_protection_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -272,6 +272,7 @@ ActiveRecord::Schema.define(version: 2019_08_16_151221) do
|
|||
t.boolean "lock_memberships_to_ldap", default: false, null: false
|
||||
t.boolean "time_tracking_limit_to_hours", default: false, null: false
|
||||
t.string "grafana_url", default: "/-/grafana", null: false
|
||||
t.boolean "login_recaptcha_protection_enabled", default: false, null: false
|
||||
t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
|
||||
t.integer "raw_blob_request_limit", default: 300, null: false
|
||||
t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false
|
||||
|
|
|
@ -104,6 +104,11 @@ module API
|
|||
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
|
||||
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
|
||||
end
|
||||
optional :login_recaptcha_protection_enabled, type: Boolean, desc: 'Helps prevent brute-force attacks'
|
||||
given login_recaptcha_protection_enabled: ->(val) { val } do
|
||||
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
|
||||
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
|
||||
end
|
||||
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
|
||||
optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
|
||||
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
|
||||
|
|
39
lib/gitlab/anonymous_session.rb
Normal file
39
lib/gitlab/anonymous_session.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class AnonymousSession
|
||||
def initialize(remote_ip, session_id: nil)
|
||||
@remote_ip = remote_ip
|
||||
@session_id = session_id
|
||||
end
|
||||
|
||||
def store_session_id_per_ip
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.pipelined do
|
||||
redis.sadd(session_lookup_name, session_id)
|
||||
redis.expire(session_lookup_name, 24.hours)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stored_sessions
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.scard(session_lookup_name)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_session_per_ip_entries
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.srem(session_lookup_name, session_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :remote_ip, :session_id
|
||||
|
||||
def session_lookup_name
|
||||
@session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@
|
|||
module Gitlab
|
||||
module Recaptcha
|
||||
def self.load_configurations!
|
||||
if Gitlab::CurrentSettings.recaptcha_enabled
|
||||
if Gitlab::CurrentSettings.recaptcha_enabled || enabled_on_login?
|
||||
::Recaptcha.configure do |config|
|
||||
config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
|
||||
config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
|
||||
|
@ -16,5 +16,9 @@ module Gitlab
|
|||
def self.enabled?
|
||||
Gitlab::CurrentSettings.recaptcha_enabled
|
||||
end
|
||||
|
||||
def self.enabled_on_login?
|
||||
Gitlab::CurrentSettings.login_recaptcha_protection_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ module Gitlab
|
|||
SESSION_NAMESPACE = 'session:gitlab'.freeze
|
||||
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze
|
||||
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'.freeze
|
||||
IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'.freeze
|
||||
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
|
||||
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
|
||||
|
||||
|
|
|
@ -4321,7 +4321,7 @@ msgstr ""
|
|||
msgid "Enable or disable version check and usage ping."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable reCAPTCHA or Akismet and set IP limits."
|
||||
msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable shared Runners"
|
||||
|
@ -5691,7 +5691,10 @@ msgstr ""
|
|||
msgid "Help page text and support page url."
|
||||
msgstr ""
|
||||
|
||||
msgid "Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}"
|
||||
msgid "Helps prevent bots from brute-force attacks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Helps prevent bots from creating accounts."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide archived projects"
|
||||
|
|
|
@ -100,16 +100,8 @@ describe SessionsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when reCAPTCHA is enabled' do
|
||||
let(:user) { create(:user) }
|
||||
let(:user_params) { { login: user.username, password: user.password } }
|
||||
|
||||
before do
|
||||
stub_application_setting(recaptcha_enabled: true)
|
||||
request.headers[described_class::CAPTCHA_HEADER] = 1
|
||||
end
|
||||
|
||||
it 'displays an error when the reCAPTCHA is not solved' do
|
||||
context 'with reCAPTCHA' do
|
||||
def unsuccesful_login(user_params, sesion_params: {})
|
||||
# Without this, `verify_recaptcha` arbitrarily returns true in test env
|
||||
Recaptcha.configuration.skip_verify_env.delete('test')
|
||||
counter = double(:counter)
|
||||
|
@ -119,14 +111,10 @@ describe SessionsController do
|
|||
.with(:failed_login_captcha_total, anything)
|
||||
.and_return(counter)
|
||||
|
||||
post(:create, params: { user: user_params })
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
|
||||
expect(subject.current_user).to be_nil
|
||||
post(:create, params: { user: user_params }, session: sesion_params)
|
||||
end
|
||||
|
||||
it 'successfully logs in a user when reCAPTCHA is solved' do
|
||||
def succesful_login(user_params, sesion_params: {})
|
||||
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
|
||||
Recaptcha.configuration.skip_verify_env << 'test'
|
||||
counter = double(:counter)
|
||||
|
@ -137,9 +125,80 @@ describe SessionsController do
|
|||
.and_return(counter)
|
||||
expect(Gitlab::Metrics).to receive(:counter).and_call_original
|
||||
|
||||
post(:create, params: { user: user_params })
|
||||
post(:create, params: { user: user_params }, session: sesion_params)
|
||||
end
|
||||
|
||||
expect(subject.current_user).to eq user
|
||||
context 'when reCAPTCHA is enabled' do
|
||||
let(:user) { create(:user) }
|
||||
let(:user_params) { { login: user.username, password: user.password } }
|
||||
|
||||
before do
|
||||
stub_application_setting(recaptcha_enabled: true)
|
||||
request.headers[described_class::CAPTCHA_HEADER] = 1
|
||||
end
|
||||
|
||||
it 'displays an error when the reCAPTCHA is not solved' do
|
||||
# Without this, `verify_recaptcha` arbitrarily returns true in test env
|
||||
|
||||
unsuccesful_login(user_params)
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
|
||||
expect(subject.current_user).to be_nil
|
||||
end
|
||||
|
||||
it 'successfully logs in a user when reCAPTCHA is solved' do
|
||||
succesful_login(user_params)
|
||||
|
||||
expect(subject.current_user).to eq user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reCAPTCHA login protection is enabled' do
|
||||
let(:user) { create(:user) }
|
||||
let(:user_params) { { login: user.username, password: user.password } }
|
||||
|
||||
before do
|
||||
stub_application_setting(login_recaptcha_protection_enabled: true)
|
||||
end
|
||||
|
||||
context 'when user tried to login 5 times' do
|
||||
it 'displays an error when the reCAPTCHA is not solved' do
|
||||
unsuccesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
|
||||
expect(subject.current_user).to be_nil
|
||||
end
|
||||
|
||||
it 'successfully logs in a user when reCAPTCHA is solved' do
|
||||
succesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
|
||||
|
||||
expect(subject.current_user).to eq user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are more than 5 anonymous session with the same IP' do
|
||||
before do
|
||||
allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :stored_sessions).and_return(6)
|
||||
end
|
||||
|
||||
it 'displays an error when the reCAPTCHA is not solved' do
|
||||
unsuccesful_login(user_params)
|
||||
|
||||
expect(response).to render_template(:new)
|
||||
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
|
||||
expect(subject.current_user).to be_nil
|
||||
end
|
||||
|
||||
it 'successfully logs in a user when reCAPTCHA is solved' do
|
||||
expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_entries)
|
||||
|
||||
succesful_login(user_params)
|
||||
|
||||
expect(subject.current_user).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -348,4 +407,17 @@ describe SessionsController do
|
|||
expect(controller.stored_location_for(:redirect)).to eq(search_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when login fails' do
|
||||
before do
|
||||
set_devise_mapping(context: @request)
|
||||
@request.env["warden.options"] = { action: 'unauthenticated' }
|
||||
end
|
||||
|
||||
it 'does increment failed login counts for session' do
|
||||
get(:new, params: { user: { login: 'failed' } })
|
||||
|
||||
expect(session[:failed_login_attempts]).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -263,6 +263,7 @@ describe 'Admin updates settings' do
|
|||
|
||||
page.within('.as-spam') do
|
||||
check 'Enable reCAPTCHA'
|
||||
check 'Enable reCAPTCHA for login'
|
||||
fill_in 'reCAPTCHA Site Key', with: 'key'
|
||||
fill_in 'reCAPTCHA Private Key', with: 'key'
|
||||
fill_in 'IPs per user', with: 15
|
||||
|
@ -271,6 +272,7 @@ describe 'Admin updates settings' do
|
|||
|
||||
expect(page).to have_content "Application settings saved successfully"
|
||||
expect(current_settings.recaptcha_enabled).to be true
|
||||
expect(current_settings.login_recaptcha_protection_enabled).to be true
|
||||
expect(current_settings.unique_ips_limit_per_user).to eq(15)
|
||||
end
|
||||
end
|
||||
|
|
78
spec/lib/gitlab/anonymous_session_spec.rb
Normal file
78
spec/lib/gitlab/anonymous_session_spec.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
|
||||
let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
|
||||
let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
|
||||
|
||||
subject { new_anonymous_session }
|
||||
|
||||
def new_anonymous_session(session_id = default_session_id)
|
||||
described_class.new('127.0.0.1', session_id: session_id)
|
||||
end
|
||||
|
||||
describe '#store_session_id_per_ip' do
|
||||
it 'adds session id to proper key' do
|
||||
subject.store_session_id_per_ip
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds expiration time to key' do
|
||||
Timecop.freeze do
|
||||
subject.store_session_id_per_ip
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.ttl("session:lookup:ip:gitlab:127.0.0.1")).to eq(24.hours.to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds id only once' do
|
||||
subject.store_session_id_per_ip
|
||||
subject.store_session_id_per_ip
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is already one session' do
|
||||
it 'adds session id to proper key' do
|
||||
subject.store_session_id_per_ip
|
||||
new_anonymous_session(additional_session_id).store_session_id_per_ip
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to contain_exactly(default_session_id, additional_session_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stored_sessions' do
|
||||
it 'returns all anonymous sessions per ip' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
|
||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
|
||||
end
|
||||
|
||||
expect(subject.stored_sessions).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes obsolete lookup through ip entries' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
|
||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
|
||||
end
|
||||
|
||||
subject.cleanup_session_per_ip_entries
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [additional_session_id]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ describe 'devise/shared/_signin_box' do
|
|||
assign(:ldap_servers, [])
|
||||
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
|
||||
allow(view).to receive(:captcha_enabled?).and_return(false)
|
||||
allow(view).to receive(:captcha_on_login_required?).and_return(false)
|
||||
end
|
||||
|
||||
it 'is shown when Crowd is enabled' do
|
||||
|
|
Loading…
Reference in a new issue