Commit 9f139538 authored by Drew Blessing's avatar Drew Blessing Committed by Drew Blessing

Report auth events in manage stage usage ping

Provide aggregate auth event details in usage ping
to help identify how often particular authentication
methods are being used. This will help inform decision
making about improvements and fixes.
parent 152b0cd9
# frozen_string_literal: true
class AuthenticationEvent < ApplicationRecord
include UsageStatistics
belongs_to :user, optional: true
validates :provider, :user_name, :result, presence: true
......@@ -9,4 +11,11 @@ class AuthenticationEvent < ApplicationRecord
failed: 0,
success: 1
}
scope :for_provider, ->(provider) { where(provider: provider) }
scope :ldap, -> { where('provider LIKE ?', 'ldap%')}
def self.providers
distinct.pluck(:provider)
end
end
---
title: Report auth events in manage stage usage ping
merge_request: 39747
author:
type: added
# frozen_string_literal: true
class AddResultIndexToAuthenticationEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_authentication_events_on_provider_user_id_created_at'
disable_ddl_transaction!
def up
add_concurrent_index :authentication_events, [:provider, :user_id, :created_at], where: 'result = 1', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :authentication_events, INDEX_NAME
end
end
aef52404e6ce83d5d4b3de65ad00b665334f5ff2e5b7b6c3c622f79313657f26
\ No newline at end of file
......@@ -19478,6 +19478,8 @@ CREATE UNIQUE INDEX index_atlassian_identities_on_extern_uid ON atlassian_identi
CREATE INDEX index_authentication_events_on_provider ON authentication_events USING btree (provider);
CREATE INDEX index_authentication_events_on_provider_user_id_created_at ON authentication_events USING btree (provider, user_id, created_at) WHERE (result = 1);
CREATE INDEX index_authentication_events_on_user_id ON authentication_events USING btree (user_id);
CREATE INDEX index_award_emoji_on_awardable_type_and_awardable_id ON award_emoji USING btree (awardable_type, awardable_id);
......
......@@ -542,6 +542,7 @@ module Gitlab
groups: distinct_count(::GroupMember.where(time_period), :user_id),
users_created: count(::User.where(time_period), start: user_minimum_id, finish: user_maximum_id),
omniauth_providers: filtered_omniauth_provider_names.reject { |name| name == 'group_saml' },
user_auth_by_provider: distinct_count_user_auth_by_provider(time_period),
projects_imported: {
gitlab_project: projects_imported_count('gitlab_project', time_period),
gitlab: projects_imported_count('gitlab', time_period),
......@@ -813,6 +814,7 @@ module Gitlab
clear_memoization(:approval_merge_request_rule_maximum_id)
clear_memoization(:project_minimum_id)
clear_memoization(:project_maximum_id)
clear_memoization(:auth_providers)
end
# rubocop: disable CodeReuse/ActiveRecord
......@@ -844,6 +846,39 @@ module Gitlab
def projects_imported_count(from, time_period)
distinct_count(::Project.imported_from(from).where(time_period), :creator_id) # rubocop: disable CodeReuse/ActiveRecord
end
# rubocop:disable CodeReuse/ActiveRecord
def distinct_count_user_auth_by_provider(time_period)
counts = auth_providers_except_ldap.each_with_object({}) do |provider, hash|
hash[provider] = distinct_count(
::AuthenticationEvent.success.for_provider(provider).where(time_period), :user_id)
end
if any_ldap_auth_providers?
counts['ldap'] = distinct_count(
::AuthenticationEvent.success.ldap.where(time_period), :user_id
)
end
counts
end
# rubocop:enable CodeReuse/ActiveRecord
# rubocop:disable UsageData/LargeTable
def auth_providers
strong_memoize(:auth_providers) do
::AuthenticationEvent.providers
end
end
# rubocop:enable UsageData/LargeTable
def auth_providers_except_ldap
auth_providers.reject { |provider| provider.starts_with?('ldap') }
end
def any_ldap_auth_providers?
auth_providers.any? { |provider| provider.starts_with?('ldap') }
end
end
end
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :authentication_event do
user
provider { :standard }
user_name { 'Jane Doe' }
ip_address { '127.0.0.1' }
result { :failed }
end
end
......@@ -29,7 +29,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
user_minimum_id user_maximum_id unique_visit_service
deployment_minimum_id deployment_maximum_id
approval_merge_request_rule_minimum_id
approval_merge_request_rule_maximum_id)
approval_merge_request_rule_maximum_id
auth_providers)
values.each do |key|
expect(described_class).to receive(:clear_memoization).with(key)
end
......@@ -167,6 +168,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe 'usage_activity_by_stage_manage' do
it 'includes accurate usage_activity_by_stage data' do
described_class.clear_memoization(:auth_providers)
stub_config(
omniauth:
{ providers: omniauth_providers }
......@@ -174,21 +177,29 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
for_defined_days_back do
user = create(:user)
user2 = create(:user)
create(:event, author: user)
create(:group_member, user: user)
create(:authentication_event, user: user, provider: :ldapmain, result: :success)
create(:authentication_event, user: user2, provider: :ldapsecondary, result: :success)
create(:authentication_event, user: user2, provider: :group_saml, result: :success)
create(:authentication_event, user: user2, provider: :group_saml, result: :success)
create(:authentication_event, user: user, provider: :group_saml, result: :failed)
end
expect(described_class.usage_activity_by_stage_manage({})).to include(
events: 2,
groups: 2,
users_created: 4,
omniauth_providers: ['google_oauth2']
users_created: 6,
omniauth_providers: ['google_oauth2'],
user_auth_by_provider: { 'group_saml' => 2, 'ldap' => 4 }
)
expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include(
events: 1,
groups: 1,
users_created: 2,
omniauth_providers: ['google_oauth2']
users_created: 3,
omniauth_providers: ['google_oauth2'],
user_auth_by_provider: { 'group_saml' => 1, 'ldap' => 2 }
)
end
......
......@@ -12,4 +12,35 @@ RSpec.describe AuthenticationEvent do
it { is_expected.to validate_presence_of(:user_name) }
it { is_expected.to validate_presence_of(:result) }
end
describe 'scopes' do
let_it_be(:ldap_event) { create(:authentication_event, provider: :ldapmain, result: :failed) }
let_it_be(:google_oauth2) { create(:authentication_event, provider: :google_oauth2, result: :success) }
describe '.for_provider' do
it 'returns events only for the specified provider' do
expect(described_class.for_provider(:ldapmain)).to match_array ldap_event
end
end
describe '.ldap' do
it 'returns all events for an LDAP provider' do
expect(described_class.ldap).to match_array ldap_event
end
end
end
describe '.providers' do
before do
create(:authentication_event, provider: :ldapmain)
create(:authentication_event, provider: :google_oauth2)
create(:authentication_event, provider: :standard)
create(:authentication_event, provider: :standard)
create(:authentication_event, provider: :standard)
end
it 'returns an array of distinct providers' do
expect(described_class.providers).to match_array %w(ldapmain google_oauth2 standard)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment