Commit 2f6b1b58 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'tc-namespace-license-checks--audit-events' into 'master'

Namespace license checks Audit Events & Admin Audit Log

Closes #2583

See merge request !2326
parents 4a5bc019 32db5174
class Admin::AuditLogsController < Admin::ApplicationController
before_action :check_license_admin_audit_log_available!
def index
@events = LogFinder.new(audit_logs_params).execute
@entity = case audit_logs_params[:event_type]
......@@ -16,4 +18,8 @@ class Admin::AuditLogsController < Admin::ApplicationController
def audit_logs_params
params.permit(:page, :event_type, :user_id, :project_id, :group_id)
end
def check_license_admin_audit_log_available!
render_404 unless License.feature_available?(:admin_audit_log)
end
end
class Groups::AuditEventsController < Groups::ApplicationController
before_action :authorize_admin_group!
before_action :check_audit_events_available!
layout 'group_settings'
......
class Projects::AuditEventsController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :check_audit_events_available!
layout 'project_settings'
......
class License < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
ADMIN_AUDIT_LOG_FEATURE = 'GitLab_AdminAuditLog'.freeze
AUDIT_EVENTS_FEATURE = 'GitLab_AuditEvents'.freeze
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
BURNDOWN_CHARTS_FEATURE = 'GitLab_BurndownCharts'.freeze
CONTRIBUTION_ANALYTICS_FEATURE = 'GitLab_ContributionAnalytics'.freeze
......@@ -28,6 +30,7 @@ class License < ActiveRecord::Base
VARIABLE_ENVIRONMENT_SCOPE_FEATURE = 'GitLab_VariableEnvironmentScope'.freeze
FEATURE_CODES = {
admin_audit_log: ADMIN_AUDIT_LOG_FEATURE,
auditor_user: AUDITOR_USER_FEATURE,
elastic_search: ELASTIC_SEARCH_FEATURE,
geo: GEO_FEATURE,
......@@ -37,6 +40,7 @@ class License < ActiveRecord::Base
variable_environment_scope: VARIABLE_ENVIRONMENT_SCOPE_FEATURE,
# Features that make sense to Namespace:
audit_events: AUDIT_EVENTS_FEATURE,
burndown_charts: BURNDOWN_CHARTS_FEATURE,
contribution_analytics: CONTRIBUTION_ANALYTICS_FEATURE,
deploy_board: DEPLOY_BOARD_FEATURE,
......@@ -63,6 +67,7 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [
{ AUDIT_EVENTS_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 },
......@@ -85,6 +90,7 @@ class License < ActiveRecord::Base
EEP_FEATURES = [
*EES_FEATURES,
{ ADMIN_AUDIT_LOG_FEATURE => 1 },
{ AUDITOR_USER_FEATURE => 1 },
{ DEPLOY_BOARD_FEATURE => 1 },
{ FILE_LOCKS_FEATURE => 1 },
......@@ -106,6 +112,7 @@ class License < ActiveRecord::Base
# Early adopters should not earn new features as they're
# introduced.
EARLY_ADOPTER_FEATURES = [
{ AUDIT_EVENTS_FEATURE => 1 },
{ AUDITOR_USER_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
......
class AuditEventService
prepend EE::AuditEventService
def initialize(author, entity, details = {})
@author, @entity, @details = author, entity, details
end
def for_member(member)
action = @details[:action]
old_access_level = @details[:old_access_level]
author_name = @author.name
user_id = member.id
user_name = member.user ? member.user.name : 'Deleted User'
@details =
case action
when :destroy
{
remove: "user_access",
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
when :create
{
add: "user_access",
as: Gitlab::Access.options_with_owner.key(member.access_level.to_i),
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
when :update, :override
{
change: "access_level",
from: old_access_level,
to: member.human_access,
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
end
self
end
def for_deploy_key(key_title)
action = @details[:action]
author_name = @author.name
@details =
case action
when :destroy
{
remove: "deploy_key",
author_name: author_name,
target_id: key_title,
target_type: "DeployKey",
target_details: key_title
}
when :create
{
add: "deploy_key",
author_name: author_name,
target_id: key_title,
target_type: "DeployKey",
target_details: key_title
}
end
self
end
def for_authentication
@details = {
with: @details[:with],
......@@ -87,8 +21,7 @@ class AuditEventService
author_id: @author.id,
entity_id: @entity.id,
entity_type: @entity.class.name,
details: @details.merge(ip_address: @author.current_sign_in_ip,
entity_path: @entity.full_path)
details: @details
)
end
end
module EE
module AuditEventService
def for_member(member)
action = @details[:action]
old_access_level = @details[:old_access_level]
author_name = @author.name
user_id = member.id
user_name = member.user ? member.user.name : 'Deleted User'
@details =
case action
when :destroy
{
remove: "user_access",
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
when :create
{
add: "user_access",
as: ::Gitlab::Access.options_with_owner.key(member.access_level.to_i),
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
when :update, :override
{
change: "access_level",
from: old_access_level,
to: member.human_access,
author_name: author_name,
target_id: user_id,
target_type: "User",
target_details: user_name
}
end
self
end
def for_deploy_key(key_title)
action = @details[:action]
author_name = @author.name
@details =
case action
when :destroy
{
remove: "deploy_key",
author_name: author_name,
target_id: key_title,
target_type: "DeployKey",
target_details: key_title
}
when :create
{
add: "deploy_key",
author_name: author_name,
target_id: key_title,
target_type: "DeployKey",
target_details: key_title
}
end
self
end
def security_event
if admin_audit_log_enabled?
add_security_event_admin_details!
return super
end
super if audit_events_enabled?
end
def add_security_event_admin_details!
@details.merge!(ip_address: @author.current_sign_in_ip,
entity_path: @entity.full_path)
end
def audit_events_enabled?
return true unless @entity.respond_to?(:feature_available?)
@entity.feature_available?(:audit_events)
end
def admin_audit_log_enabled?
License.feature_available?(:admin_audit_log)
end
end
end
......@@ -27,7 +27,4 @@
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do
%span
Requests Profiles
= nav_link path: 'audit_logs#index' do
= link_to admin_audit_logs_path, title: 'Audit Log' do
%span
Audit Log
= render 'admin/monitoring/ee/nav'
- if License.feature_available?(:admin_audit_log)
= nav_link path: 'audit_logs#index' do
= link_to admin_audit_logs_path, title: 'Audit Log' do
%span
Audit Log
......@@ -10,10 +10,11 @@
%span
Webhooks
= nav_link(path: 'audit_events#index') do
= link_to group_audit_events_path(@group), title: 'Audit Events' do
%span
Audit Events
- if @group.feature_available?(:audit_events)
= nav_link(path: 'audit_events#index') do
= link_to group_audit_events_path(@group), title: 'Audit Events' do
%span
Audit Events
- if @group.shared_runners_enabled? && @group.shared_runners_minutes_limit_enabled?
= nav_link(path: 'pipeline_quota#index') do
......
......@@ -29,7 +29,4 @@
%span
Pages
= nav_link(controller: :audit_events) do
= link_to project_audit_events_path(@project), title: "Audit Events" do
%span
Audit Events
= render 'projects/settings/ee/nav'
- if @project.feature_available?(:audit_events)
= nav_link(controller: :audit_events) do
= link_to project_audit_events_path(@project), title: "Audit Events" do
%span
Audit Events
---
title: Namespace license checks Audit Events & Admin Audit Log
merge_request: 2326
author:
......@@ -9,75 +9,99 @@ describe 'Admin::AuditLogs', feature: true, js: true do
sign_in(create(:admin))
end
describe 'user events' do
context 'unlicensed' do
before do
AuditEventService.new(user, user, with: :ldap)
.for_authentication.security_event
stub_licensed_features(admin_audit_log: false)
end
it 'returns 404' do
visit admin_audit_logs_path
expect(page.status_code).to eq(404)
end
end
it 'filters by user' do
filter_by_type('User Events')
context 'licensed' do
before do
stub_licensed_features(admin_audit_log: true)
end
click_button 'User'
wait_for_requests
it 'has Audit Log button in head nav bar' do
visit admin_audit_logs_path
within '.dropdown-menu-user' do
click_link user.name
expect(page).to have_link('Audit Log', href: admin_audit_logs_path)
end
describe 'user events' do
before do
AuditEventService.new(user, user, with: :ldap)
.for_authentication.security_event
visit admin_audit_logs_path
end
wait_for_requests
it 'filters by user' do
filter_by_type('User Events')
expect(page).to have_content('Signed in with LDAP authentication')
end
end
click_button 'User'
wait_for_requests
describe 'group events' do
let(:group_member) { create(:group_member, user: user) }
within '.dropdown-menu-user' do
click_link user.name
end
before do
AuditEventService.new(user, group_member.group, { action: :create })
.for_member(group_member).security_event
wait_for_requests
visit admin_audit_logs_path
expect(page).to have_content('Signed in with LDAP authentication')
end
end
it 'filters by group' do
filter_by_type('Group Events')
describe 'group events' do
let(:group_member) { create(:group_member, user: user) }
click_button 'Group'
find('.group-item-select').click
wait_for_requests
find('.select2-results').click
before do
AuditEventService.new(user, group_member.group, { action: :create })
.for_member(group_member).security_event
find('#events-table td', match: :first)
visit admin_audit_logs_path
end
expect(page).to have_content('Added user access as Owner')
end
end
it 'filters by group' do
filter_by_type('Group Events')
describe 'project events' do
let(:project_member) { create(:project_member, user: user) }
click_button 'Group'
find('.group-item-select').click
wait_for_requests
find('.select2-results').click
before do
AuditEventService.new(user, project_member.project, { action: :destroy })
.for_member(project_member).security_event
find('#events-table td', match: :first)
visit admin_audit_logs_path
expect(page).to have_content('Added user access as Owner')
end
end
it 'filters by project' do
filter_by_type('Project Events')
describe 'project events' do
let(:project_member) { create(:project_member, user: user) }
before do
AuditEventService.new(user, project_member.project, { action: :destroy })
.for_member(project_member).security_event
visit admin_audit_logs_path
end
it 'filters by project' do
filter_by_type('Project Events')
click_button 'Project'
find('.project-item-select').click
wait_for_requests
find('.select2-results').click
click_button 'Project'
find('.project-item-select').click
wait_for_requests
find('.select2-results').click
find('#events-table td', match: :first)
find('#events-table td', match: :first)
expect(page).to have_content('Removed user access')
expect(page).to have_content('Removed user access')
end
end
end
......
......@@ -11,6 +11,30 @@ feature 'Groups > Audit Events', js: true, feature: true do
sign_in(user)
end
context 'unlicensed' do
before do
stub_licensed_features(audit_events: false)
end
it 'returns 404' do
visit group_audit_events_path(group)
expect(page.status_code).to eq(404)
end
it 'does not have Audit Events button in head nav bar' do
visit edit_group_path(group)
expect(page).not_to have_link('Audit Events')
end
end
it 'has Audit Events button in head nav bar' do
visit edit_group_path(group)
expect(page).to have_link('Audit Events')
end
describe 'changing a user access level' do
it "appears in the group's audit events" do
visit group_group_members_path(group)
......
......@@ -10,8 +10,34 @@ feature 'Projects > Audit Events', js: true, feature: true do
sign_in(user)
end
context 'unlicensed' do
before do
stub_licensed_features(audit_events: false)
end
it 'returns 404' do
visit project_audit_events_path(project)
expect(page.status_code).to eq(404)
end
it 'does not have Audit Events button in head nav bar' do
visit edit_project_path(project)
expect(page).not_to have_link('Audit Events')
end
end
it 'has Audit Events button in head nav bar' do
visit edit_project_path(project)
expect(page).to have_link('Audit Events')
end
describe 'adding an SSH key' do
it "appears in the project's audit events" do
stub_licensed_features(audit_events: true)
visit new_project_deploy_key_path(project)
fill_in 'deploy_key_title', with: 'laptop'
......
......@@ -18,15 +18,86 @@ describe AuditEventService, services: true do
event = service.for_member(project_member).security_event
expect(event[:details][:target_details]).to eq('Deleted User')
end
it 'has the IP address' do
event = service.for_member(project_member).security_event
expect(event[:details][:ip_address]).to eq(user.current_sign_in_ip)
end
it 'has the entity full path' do
event = service.for_member(project_member).security_event
expect(event[:details][:entity_path]).to eq(project.full_path)
context 'admin audit log licensed' do
before do
stub_licensed_features(admin_audit_log: true)
end
it 'has the entity full path' do
event = service.for_member(project_member).security_event
expect(event[:details][:entity_path]).to eq(project.full_path)
end
end
end
describe '#security_event' do
context 'unlicensed' do
before do
stub_licensed_features(audit_events: false)
end
it 'does not create an event' do
expect(SecurityEvent).not_to receive(:create)
service.security_event
end
end
context 'licensed' do
it 'creates an event' do
expect { service.security_event }.to change(SecurityEvent, :count).by(1)
end
end
end
describe '#audit_events_enabled?' do
context 'entity is a project' do
let(:service) { described_class.new(user, project, { action: :destroy }) }
it 'returns false when project is unlicensed' do
stub_licensed_features(audit_events: false)
expect(service.audit_events_enabled?).to be_falsy
end
it 'returns true when project is licensed' do
stub_licensed_features(audit_events: true)
expect(service.audit_events_enabled?).to be_truthy
end
end
context 'entity is a group' do
let(:group) { create(:group) }
let(:service) { described_class.new(user, group, { action: :destroy }) }
it 'returns false when group is unlicensed' do
stub_licensed_features(audit_events: false)
expect(service.audit_events_enabled?).to be_falsy
end
it 'returns true when group is licensed' do
stub_licensed_features(audit_events: true)
expect(service.audit_events_enabled?).to be_truthy
end
end
context 'entity is a user' do
let(:service) { described_class.new(user, user, { action: :destroy }) }
it 'returns true when unlicensed' do
stub_licensed_features(audit_events: false, admin_audit_log: false)
expect(service.audit_events_enabled?).to be_truthy
end
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