Commit 7a491a94 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'ee/master' into ce-to-ee-2017-11-03

* ee/master: (22 commits)
  Add project actions in Audit events
  Update EE-specific API helpers spec
  Pull API job token behavior in line with other tokens, and expand test coverage
  Update documentation
  Add changelog items
  Update specs for sudo behavior
  Move RSS and incoming email tokens from User Settings > Accounts to User Settings > Access Tokens
  Remove user authentication_token column
  Migrate user private tokens to personal access tokens
  Add sudo API scope
  Consistently use PersonalAccessToken instead of PersonalToken
  Remove User#private_token
  Remove authentication using user.private_token
  Remove tokens:reset_all_auth rake task
  Remove gitlab:users:clear_all_authentication_tokens rake task
  Remove private_token from API user entity
  Remove Session API
  Remove Private Token from User Settings > Account
  Update CHANGELOG.md for 10.1.1
  Update CHANGELOG-EE.md for 10.1.1-ee
  ...
parents 8dcff73e e40b800a
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 10.1.1 (2017-10-31)
- No changes.
- [FIXED] Fix LDAP group sync for nested groups e.g. when base has uppercase or extraneous spaces. !3217
- [FIXED] Geo: read-only safeguards was not working on Secondary node. !3227
- [FIXED] fix height of rebase and approve buttons.
- [FIXED] Move group boards routes under - and remove "boards" from reserved paths.
## 10.1.0 (2017-10-22) ## 10.1.0 (2017-10-22)
- [SECURITY] Prevent Related Issues from leaking confidential issues. !541 - [SECURITY] Prevent Related Issues from leaking confidential issues. !541
......
...@@ -2,6 +2,31 @@ ...@@ -2,6 +2,31 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.1.1 (2017-10-31)
- No changes.
- [FIXED] Auto Devops kubernetes default namespace is now correctly built out of gitlab project group-name. !14642 (Mircea Danila Dumitrescu)
- [FIXED] Forbid the usage of `Redis#keys`. !14889
- [FIXED] Make the circuitbreaker more robust by adding higher thresholds, and multiple access attempts. !14933
- [FIXED] Only cache last push event for existing projects when pushing to a fork. !14989
- [FIXED] Fix bug preventing secondary emails from being confirmed. !15010
- [FIXED] Fix broken wiki pages that link to a wiki file. !15019
- [FIXED] Don't rename paths that were freed up when upgrading. !15029
- [FIXED] Fix bitbucket login. !15051
- [FIXED] Update gitaly in Gitlab 10.1 to 0.43.1 for temp file cleanup. !15055
- [FIXED] Use the correct visibility attribute for projects in system hooks. !15065
- [FIXED] Normalize LDAP DN when looking up identity.
- [FIXED] Adds callback functions for initial request in clusters page.
- [FIXED] Fix missing Import/Export issue assignees.
- [FIXED] Allow boards as top level route.
- [FIXED] Fix widget of locked merge requests not being presented.
- [FIXED] Fix editing issue description in mobile view.
- [FIXED] Fix deletion of container registry or images returning an error.
- [FIXED] Fix the writing of invalid environment refs.
- [CHANGED] Store circuitbreaker settings in the database instead of config. !14842
- [CHANGED] Update default disabled merge request widget message to reflect a general failure. !14960
- [PERFORMANCE] Stop merge requests with thousands of commits from timing out. !15063
## 10.1.0 (2017-10-22) ## 10.1.0 (2017-10-22)
- [SECURITY] Use a timeout on certain git operations. !14872 - [SECURITY] Use a timeout on certain git operations. !14872
......
class AuditEvent < ActiveRecord::Base class AuditEvent < ActiveRecord::Base
prepend EE::AuditEvent
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id belongs_to :user, foreign_key: :author_id
...@@ -9,15 +11,11 @@ class AuditEvent < ActiveRecord::Base ...@@ -9,15 +11,11 @@ class AuditEvent < ActiveRecord::Base
after_initialize :initialize_details after_initialize :initialize_details
def author_name
details[:author_name].blank? ? user&.name : details[:author_name]
end
def initialize_details def initialize_details
self.details = {} if details.nil? self.details = {} if details.nil?
end end
def present def author_name
AuditEventPresenter.new(self) self.user.name
end end
end end
...@@ -3,19 +3,31 @@ AuditEventPresenter < Gitlab::View::Presenter::Simple ...@@ -3,19 +3,31 @@ AuditEventPresenter < Gitlab::View::Presenter::Simple
presents :audit_event presents :audit_event
def author_name def author_name
audit_event.author_name || '(removed)' user = audit_event.user
return nil unless user
link_to(user.name, user_path(user))
end end
def target def target
audit_event.details[:target_details] details[:target_details]
end end
def ip_address def ip_address
audit_event.details[:ip_address] details[:ip_address]
end
def details
audit_event.details
end end
def object def object
audit_event.details[:entity_path] entity = audit_event.entity
return nil unless entity
link_to(details[:entity_path], entity).html_safe
end end
def date def date
...@@ -23,6 +35,16 @@ AuditEventPresenter < Gitlab::View::Presenter::Simple ...@@ -23,6 +35,16 @@ AuditEventPresenter < Gitlab::View::Presenter::Simple
end end
def action def action
Audit::Details.humanize(audit_event.details) Audit::Details.humanize(details)
end
private
# The class can't include ActionView::Helpers::UrlHelper because it overwrites
# the method url_for. In this helper, that implementation of that method
# doesn't accept objects to resolve their route. That's why here we call the
# native url_for to get the route of the object and then call the link_to with it
def link_to(name, object)
ActionController::Base.helpers.link_to(name, url_for(object))
end end
end end
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
- elsif params[:event_type] == 'Project' - elsif params[:event_type] == 'Project'
.filter-item.inline .filter-item.inline
= project_select_tag(:project_id, { class: 'project-item-select hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', = project_select_tag(:project_id, { class: 'project-item-select hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: admin_project_dropdown_label('Search projects'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', allprojects: 'true'} }) placeholder: admin_project_dropdown_label('Search projects'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', all_projects: 'true', simple_filter: true} })
- elsif params[:event_type] == 'Group' - elsif params[:event_type] == 'Group'
.filter-item.inline .filter-item.inline
= groups_select_tag(:group_id, { required: true, class: 'group-item-select project-item-select hidden-filter-value', toggle_class: 'js-group-search js-group-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit', = groups_select_tag(:group_id, { required: true, class: 'group-item-select project-item-select hidden-filter-value', toggle_class: 'js-group-search js-group-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit',
...@@ -39,8 +39,16 @@ ...@@ -39,8 +39,16 @@
%tbody %tbody
- @events.map(&:present).each do |event| - @events.map(&:present).each do |event|
%tr %tr
%td= event.author_name %td
%td= event.object - if (author_link = event.author_name)
= author_link
- else
#{event.details[:author_name]} <small>(removed)</small>
%td
- if (object_link = event.object)
= object_link
- else
#{event.details[:entity_path]} <small>(removed)</small>
%td= event.action %td= event.action
%td= event.target %td= event.target
%td= event.ip_address %td= event.ip_address
......
---
title: Add project actions in Audit events
merge_request: 3160
author:
type: changed
---
title: fix height of rebase and approve buttons
merge_request:
author:
type: fixed
---
title: 'Geo: read-only safeguards was not working on Secondary node'
merge_request: 3227
author:
type: fixed
---
title: Move group boards routes under - and remove "boards" from reserved paths
merge_request:
author:
type: fixed
---
title: Fix LDAP group sync for nested groups e.g. when base has uppercase or extraneous spaces
merge_request: 3217
author:
type: fixed
---
title: Update default disabled merge request widget message to reflect a general failure
merge_request: 14960
author:
type: changed
---
title: 'Fix bug preventing secondary emails from being confirmed'
merge_request: 15010
author:
type: fixed
---
title: Fix editing issue description in mobile view
merge_request:
author:
type: fixed
---
title: Fix bitbucket login
merge_request: 15051
author:
type: fixed
---
title: Make the circuitbreaker more robust by adding higher thresholds, and multiple
access attempts.
merge_request: 14933
author:
type: fixed
---
title: Store circuitbreaker settings in the database instead of config
merge_request: 14842
author:
type: changed
---
title: Forbid the usage of `Redis#keys`
merge_request: 14889
author:
type: fixed
---
title: Don't rename paths that were freed up when upgrading
merge_request: 15029
author:
type: fixed
---
title: Only cache last push event for existing projects when pushing to a fork
merge_request: 14989
author:
type: fixed
---
title: Use the correct visibility attribute for projects in system hooks
merge_request: 15065
author:
type: fixed
---
title: Fix broken wiki pages that link to a wiki file
merge_request: 15019
author:
type: fixed
---
title: Allow boards as top level route
merge_request:
author:
type: fixed
---
title: Auto Devops kubernetes default namespace is now correctly built out of gitlab
project group-name
merge_request: 14642
author: Mircea Danila Dumitrescu
type: fixed
---
title: Fix deletion of container registry or images returning an error
merge_request:
author:
type: fixed
---
title: Fix the writing of invalid environment refs
merge_request:
author:
type: fixed
module EE
module AuditEvent
extend ActiveSupport::Concern
def author_name
details[:author_name].blank? ? user&.name : details[:author_name]
end
def entity
return unless entity_type && entity_id
# Avoiding exception if the record doesn't exist
@entity ||= entity_type.constantize.find_by_id(entity_id)
end
def present
AuditEventPresenter.new(self)
end
end
end
...@@ -29,7 +29,7 @@ module EE ...@@ -29,7 +29,7 @@ module EE
has_many :approvers, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :approvers, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :approver_groups, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :approver_groups, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :audit_events, as: :entity, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :audit_events, as: :entity
has_many :remote_mirrors, inverse_of: :project has_many :remote_mirrors, inverse_of: :project
has_many :path_locks has_many :path_locks
......
...@@ -63,8 +63,8 @@ module EE ...@@ -63,8 +63,8 @@ module EE
to: @details[:to], to: @details[:to],
author_name: @author.name, author_name: @author.name,
target_id: @entity.id, target_id: @entity.id,
target_type: @entity.class, target_type: @entity.class.name,
target_details: @entity.name target_details: @details[:target_details] || @entity.name
} }
self self
end end
...@@ -93,6 +93,10 @@ module EE ...@@ -93,6 +93,10 @@ module EE
) )
end end
def for_project
for_custom_model('project', @entity.full_path)
end
def entity_audit_events_enabled? def entity_audit_events_enabled?
@entity.respond_to?(:feature_available?) && @entity.feature_available?(:audit_events) @entity.respond_to?(:feature_available?) && @entity.feature_available?(:audit_events)
end end
......
...@@ -20,7 +20,10 @@ module EE ...@@ -20,7 +20,10 @@ module EE
end end
end end
log_geo_event(project) if project&.persisted? if project&.persisted?
log_geo_event(project)
log_audit_event(project)
end
project project
end end
...@@ -51,6 +54,14 @@ module EE ...@@ -51,6 +54,14 @@ module EE
project.push_rule = push_rule project.push_rule = push_rule
end end
end end
def log_audit_event(project)
::AuditEventService.new(
current_user,
project,
action: :create
).for_project.security_event
end
end end
end end
end end
...@@ -9,6 +9,7 @@ module EE ...@@ -9,6 +9,7 @@ module EE
if succeeded if succeeded
mirror_cleanup(project) mirror_cleanup(project)
log_geo_event(project) log_geo_event(project)
log_audit_event(project)
end end
succeeded succeeded
...@@ -45,6 +46,16 @@ module EE ...@@ -45,6 +46,16 @@ module EE
::Geo::ProjectRegistry.where(project_id: project.id).delete_all ::Geo::ProjectRegistry.where(project_id: project.id).delete_all
end end
private
def log_audit_event(project)
::AuditEventService.new(
current_user,
project,
action: :destroy
).for_project.security_event
end
end end
end end
end end
...@@ -8,6 +8,8 @@ module EE ...@@ -8,6 +8,8 @@ module EE
super super
EE::Audit::ProjectChangesAuditor.new(@current_user, project).execute
::Geo::RepositoryRenamedEventStore.new( ::Geo::RepositoryRenamedEventStore.new(
project, project,
old_path: project.path, old_path: project.path,
......
...@@ -10,7 +10,17 @@ module EE ...@@ -10,7 +10,17 @@ module EE
params.delete(:mirror_trigger_builds) params.delete(:mirror_trigger_builds)
end end
super result = super
log_audit_events if result[:status] == :success
result
end
private
def log_audit_events
EE::Audit::ProjectChangesAuditor.new(current_user, project).execute
end end
end end
end end
......
...@@ -45,11 +45,7 @@ module API ...@@ -45,11 +45,7 @@ module API
# Helper Methods for Grape Endpoint # Helper Methods for Grape Endpoint
module HelperMethods module HelperMethods
def find_current_user! def find_current_user!
user = user = find_user_from_access_token || find_user_from_job_token || find_user_from_warden
find_user_from_access_token ||
find_user_from_warden ||
find_user_by_job_token
return unless user return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
...@@ -84,6 +80,45 @@ module API ...@@ -84,6 +80,45 @@ module API
validate_access_token! validate_access_token!
access_token.user || raise(UnauthorizedError) access_token.user || raise(UnauthorizedError)
@access_token = find_oauth_access_token || find_personal_access_token
end
def validate_access_token!(scopes: [])
return unless access_token
case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when AccessTokenValidationService::EXPIRED
raise ExpiredError
when AccessTokenValidationService::REVOKED
raise RevokedError
end
end
private
def find_user_from_access_token
return unless access_token
validate_access_token!
access_token.user || raise(UnauthorizedError)
end
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
return unless token.present?
job = Ci::Build.find_by(token: token)
raise UnauthorizedError unless job
@job_token_authentication = true
job.user
end end
# Check the Rails session for valid authentication details # Check the Rails session for valid authentication details
...@@ -100,16 +135,6 @@ module API ...@@ -100,16 +135,6 @@ module API
Gitlab::RequestForgeryProtection.verified?(env) Gitlab::RequestForgeryProtection.verified?(env)
end end
def find_user_by_job_token
return @user_by_job_token if defined?(@user_by_job_token)
@user_by_job_token =
if route_authentication_setting[:job_token_allowed]
token_string = params[JOB_TOKEN_PARAM].presence || env[JOB_TOKEN_HEADER].presence
Ci::Build.find_by_token(token_string)&.user if token_string
end
end
def route_authentication_setting def route_authentication_setting
return {} unless respond_to?(:route_setting) return {} unless respond_to?(:route_setting)
......
...@@ -408,7 +408,7 @@ module API ...@@ -408,7 +408,7 @@ module API
end end
def job_token_authentication? def job_token_authentication?
initial_current_user && initial_current_user == find_user_by_job_token initial_current_user && @job_token_authentication
end end
def warden def warden
......
module EE
module Audit
class BaseChangesAuditor
include Changes
def initialize(current_user, model)
@model = model
@current_user = current_user
end
def parse_options(column, options)
super.merge(attributes_from_auditable_model(column))
end
def attributes_from_auditable_model(column)
raise NotImplementedError
end
end
end
end
module EE
module Audit
class ProjectChangesAuditor < BaseChangesAuditor
def execute
audit_changes(:visibility_level, as: 'visibility', model: model)
audit_changes(:path, as: 'path', model: model)
audit_changes(:name, as: 'name', model: model)
audit_changes(:namespace_id, as: 'namespace', model: model)
end
def attributes_from_auditable_model(column)
case column
when :name
{
from: model.namespace.human_name + ' / ' + model.previous_changes[column].first.to_s,
to: model.full_name
}
when :path
{
from: model.old_path_with_namespace.to_s,
to: model.full_path
}
when :visibility_level
{
from: ::Gitlab::VisibilityLevel.level_name(model.previous_changes[column].first),
to: ::Gitlab::VisibilityLevel.level_name(model.previous_changes[column].last)
}
when :namespace_id
{
from: model.old_path_with_namespace,
to: model.full_path
}
end.merge(target_details: model.full_path)
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe EE::API::Helpers do describe EE::API::Helpers do
let(:env) { { 'rack.input' => StringIO.new } } include API::APIGuard::HelperMethods
include API::Helpers
let(:helper) do
Class.new { include API::Helpers, API::APIGuard::HelperMethods }.new let(:options) { {} }
let(:params) { {} }
let(:env) do
{
'rack.input' => '',
'REQUEST_METHOD' => 'GET'
}
end end
let(:header) { }
before do before do
allow(helper).to receive(:env).and_return(env)
allow(helper).to receive(:params).and_return({})
allow(helper).to receive(:options).and_return({})
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true) allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
end end
describe '#current_user' do describe '#current_user' do
let(:user) { build(:user, id: 42) } let(:user) { build(:user, id: 42) }
before do
allow(helper).to receive(:sudo!)
end
it 'handles sticking when a user could be found' do it 'handles sticking when a user could be found' do
allow(helper).to receive(:initial_current_user).and_return(user) allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(user)
expect(Gitlab::Database::LoadBalancing::RackMiddleware) expect(Gitlab::Database::LoadBalancing::RackMiddleware)
.to receive(:stick_or_unstick).with(env, :user, 42) .to receive(:stick_or_unstick).with(env, :user, 42)
helper.current_user current_user
end end
it 'does not handle sticking if no user could be found' do it 'does not handle sticking if no user could be found' do
allow(helper).to receive(:initial_current_user).and_return(nil) allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(nil)
expect(Gitlab::Database::LoadBalancing::RackMiddleware) expect(Gitlab::Database::LoadBalancing::RackMiddleware)
.not_to receive(:stick_or_unstick) .not_to receive(:stick_or_unstick)
helper.current_user current_user
end end
it 'returns the user if one could be found' do it 'returns the user if one could be found' do
allow(helper).to receive(:initial_current_user).and_return(user) allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(user)
expect(helper.current_user).to eq(user) expect(current_user).to eq(user)
end end
end end
end end
...@@ -16,6 +16,7 @@ describe Project do ...@@ -16,6 +16,7 @@ describe Project do
it { is_expected.to have_many(:path_locks) } it { is_expected.to have_many(:path_locks) }
it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_many(:source_pipelines) } it { is_expected.to have_many(:source_pipelines) }
it { is_expected.to have_many(:audit_events) }
end end
describe '#push_rule' do describe '#push_rule' do
......
...@@ -32,4 +32,71 @@ describe AuditEventService do ...@@ -32,4 +32,71 @@ describe AuditEventService do
expect(event.details[:failed_login]).to eq('LDAP') expect(event.details[:failed_login]).to eq('LDAP')
end end
end end
describe 'license' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let!(:service) { described_class.new(user, project, action: :create) }
let(:event) { service.for_project.security_event }
before do
disable_license_audit_features(service)
end
describe 'has the audit_admin feature' do
before do
allow(service).to receive(:admin_audit_log_enabled?).and_return(true)
end
it 'logs an audit event' do
expect { event }.to change(AuditEvent, :count).by(1)
end
it 'has the entity_path' do
expect(event.details[:entity_path]).to eq(project.full_path)
end
end
describe 'has the extended_audit_events feature' do
before do
allow(service).to receive(:entity_audit_events_enabled?).and_return(true)
end
it 'logs an audit event' do
expect { event }.to change(AuditEvent, :count).by(1)
end
it 'has not the entity_path' do
expect(event.details[:entity_path]).not_to eq(project.full_path)
end
end
describe 'entity has the audit_events feature' do
before do
allow(service).to receive(:audit_events_enabled?).and_return(true)
end
it 'logs an audit event' do
expect { event }.to change(AuditEvent, :count).by(1)
end
it 'has not the entity_path' do
expect(event.details[:entity_path]).not_to eq(project.full_path)
end
end
describe 'has not any audit event feature' do
it 'does not log the audit event' do
expect { event }.not_to change(AuditEvent, :count)
end
end
def disable_license_audit_features(service)
[:entity_audit_events_enabled?,
:admin_audit_log_enabled?,
:audit_events_enabled?].each do |f|
allow(service).to receive(f).and_return(false)
end
end
end
end end
...@@ -202,6 +202,29 @@ describe Projects::CreateService, '#execute' do ...@@ -202,6 +202,29 @@ describe Projects::CreateService, '#execute' do
end end
end end
context 'audit events' do
include_examples 'audit event logging' do
let(:operation) { create_project(user, opts) }
let(:fail_condition!) do
allow(Gitlab::VisibilityLevel).to receive(:allowed_for?).and_return(false)
end
let(:attributes) do
{
author_id: user.id,
entity_id: @resource.id,
entity_type: 'Project',
details: {
add: 'project',
author_name: user.name,
target_id: @resource.full_path,
target_type: 'Project',
target_details: @resource.full_path
}
}
end
end
end
def create_project(user, opts) def create_project(user, opts)
described_class.new(user, opts).execute described_class.new(user, opts).execute
end end
......
...@@ -49,4 +49,28 @@ describe Projects::DestroyService do ...@@ -49,4 +49,28 @@ describe Projects::DestroyService do
end end
end end
end end
context 'audit events' do
include_examples 'audit event logging' do
let(:operation) { subject.execute }
let(:fail_condition!) do
expect_any_instance_of(Project)
.to receive(:destroy!).and_raise(StandardError.new('Other error message'))
end
let(:attributes) do
{
author_id: user.id,
entity_id: project.id,
entity_type: 'Project',
details: {
remove: 'project',
author_name: user.name,
target_id: project.full_path,
target_type: 'Project',
target_details: project.full_path
}
}
end
end
end
end end
...@@ -18,4 +18,30 @@ describe Projects::TransferService do ...@@ -18,4 +18,30 @@ describe Projects::TransferService do
expect { subject.execute(group) }.to change(Geo::RepositoryRenamedEvent, :count).by(1) expect { subject.execute(group) }.to change(Geo::RepositoryRenamedEvent, :count).by(1)
end end
end end
context 'audit events' do
include_examples 'audit event logging' do
let(:operation) { subject.execute(group) }
let(:fail_condition!) do
expect_any_instance_of(Project)
.to receive(:has_container_registry_tags?).and_return(true)
end
let(:attributes) do
{
author_id: user.id,
entity_id: project.id,
entity_type: 'Project',
details: {
change: 'namespace',
from: project.old_path_with_namespace,
to: project.full_path,
author_name: user.name,
target_id: project.id,
target_type: 'Project',
target_details: project.full_path
}
}
end
end
end
end end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::UpdateService, '#execute' do describe Projects::UpdateService, '#execute' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, creator: user, namespace: user.namespace) } let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
context 'repository mirror' do context 'repository mirror' do
let!(:opts) do let!(:opts) do
...@@ -47,6 +47,82 @@ describe Projects::UpdateService, '#execute' do ...@@ -47,6 +47,82 @@ describe Projects::UpdateService, '#execute' do
end end
end end
context 'audit events' do
let(:audit_event_params) do
{
author_id: user.id,
entity_id: project.id,
entity_type: 'Project',
details: {
author_name: user.name,
target_id: project.id,
target_type: 'Project',
target_details: project.full_path
}
}
end
context '#name' do
include_examples 'audit event logging' do
let!(:old_name) { project.full_name }
let(:operation) { update_project(project, user, name: 'foobar') }
let(:fail_condition!) do
allow_any_instance_of(Project).to receive(:update_attributes).and_return(false)
end
let(:attributes) do
audit_event_params.tap do |param|
param[:details].merge!(
change: 'name',
from: old_name,
to: project.full_name
)
end
end
end
end
context '#path' do
include_examples 'audit event logging' do
let(:operation) { update_project(project, user, path: 'foobar1') }
let(:fail_condition!) do
allow_any_instance_of(Project).to receive(:update_attributes).and_return(false)
end
let(:attributes) do
audit_event_params.tap do |param|
param[:details].merge!(
change: 'path',
from: project.old_path_with_namespace,
to: project.full_path
)
end
end
end
end
context '#visibility' do
include_examples 'audit event logging' do
let(:operation) do
update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
let(:fail_condition!) do
allow_any_instance_of(Project).to receive(:update_attributes).and_return(false)
end
let(:attributes) do
audit_event_params.tap do |param|
param[:details].merge!(
change: 'visibility',
from: 'Private',
to: 'Internal'
)
end
end
end
end
end
def update_project(project, user, opts) def update_project(project, user, opts)
Projects::UpdateService.new(project, user, opts).execute Projects::UpdateService.new(project, user, opts).execute
end end
......
...@@ -15,7 +15,9 @@ describe Audit::Details do ...@@ -15,7 +15,9 @@ describe Audit::Details do
end end
it 'humanizes user login action' do it 'humanizes user login action' do
expect(described_class.humanize(login_action)).to eq('Signed in with LDAP authentication') string = described_class.humanize(login_action)
expect(string).to eq('Signed in with LDAP authentication')
end end
end end
...@@ -35,7 +37,9 @@ describe Audit::Details do ...@@ -35,7 +37,9 @@ describe Audit::Details do
end end
it 'humanizes add project member access action' do it 'humanizes add project member access action' do
expect(described_class.humanize(member_access_action)).to eq('Added user access as Developer') string = described_class.humanize(member_access_action)
expect(string).to eq('Added user access as Developer')
end end
end end
...@@ -56,7 +60,9 @@ describe Audit::Details do ...@@ -56,7 +60,9 @@ describe Audit::Details do
end end
it 'humanizes add group member access action' do it 'humanizes add group member access action' do
expect(described_class.humanize(member_access_action)).to eq('Changed access level from Guest to Owner') string = described_class.humanize(member_access_action)
expect(string).to eq('Changed access level from Guest to Owner')
end end
end end
...@@ -72,7 +78,9 @@ describe Audit::Details do ...@@ -72,7 +78,9 @@ describe Audit::Details do
end end
it 'humanizes the removal action' do it 'humanizes the removal action' do
expect(described_class.humanize(removal_action)).to eq('Removed deploy key') string = described_class.humanize(removal_action)
expect(string).to eq('Removed deploy key')
end end
end end
...@@ -90,7 +98,9 @@ describe Audit::Details do ...@@ -90,7 +98,9 @@ describe Audit::Details do
end end
it 'humanizes the removal action' do it 'humanizes the removal action' do
expect(described_class.humanize(action)).to eq('Changed email from a@b.com to c@b.com') string = described_class.humanize(action)
expect(string).to eq('Changed email from a@b.com to c@b.com')
end end
end end
end end
......
require 'spec_helper'
describe EE::Audit::ProjectChangesAuditor do
describe '.audit_changes' do
let!(:user) { create(:user) }
let!(:project) { create(:project, visibility_level: 0) }
let(:foo_instance) { described_class.new(user, project) }
before do
stub_licensed_features(extended_audit_events: true)
end
describe 'non audit changes' do
it 'does not call the audit event service' do
project.update!(description: 'new description')
expect { foo_instance.execute }.not_to change { SecurityEvent.count }
end
end
describe 'audit changes' do
it 'creates an event when the visibility change' do
project.update!(visibility_level: 20)
expect { foo_instance.execute }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:change]).to eq 'visibility'
end
it 'creates an event when the name change' do
project.update!(name: 'new name')
expect { foo_instance.execute }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:change]).to eq 'name'
end
it 'creates an event when the path change' do
project.update!(path: 'newpath')
expect { foo_instance.execute }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:change]).to eq 'path'
end
it 'creates an event when the namespace change' do
new_namespace = create(:namespace)
project.update!(namespace: new_namespace)
expect { foo_instance.execute }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:change]).to eq 'namespace'
end
end
end
end
...@@ -43,6 +43,26 @@ RSpec.describe AuditEvent, type: :model do ...@@ -43,6 +43,26 @@ RSpec.describe AuditEvent, type: :model do
end end
end end
describe '#entity' do
context 'when entity exists' do
let(:user) { create(:user, name: 'John Doe') }
subject(:event) { described_class.new(entity_id: user.id, entity_type: user.class.name) }
it 'returns the entity object' do
expect(event.entity).to eq user
end
end
context 'when entity does not exist' do
subject(:event) { described_class.new(entity_id: 99999, entity_type: 'User') }
it 'returns nil' do
expect(event.entity).to be_blank
end
end
end
describe '#present' do describe '#present' do
it 'returns a presenter' do it 'returns a presenter' do
expect(subject.present).to be_an_instance_of(AuditEventPresenter) expect(subject.present).to be_an_instance_of(AuditEventPresenter)
......
require 'spec_helper' require 'spec_helper'
describe AuditEventPresenter do describe AuditEventPresenter do
include Gitlab::Routing.url_helpers
let(:details) do let(:details) do
{ {
author_name: 'author', author_name: 'author',
...@@ -18,8 +20,16 @@ describe AuditEventPresenter do ...@@ -18,8 +20,16 @@ describe AuditEventPresenter do
described_class.new(audit_event) described_class.new(audit_event)
end end
it 'exposes the author name' do context 'exposes the author' do
expect(presenter.author_name).to eq(details[:author_name]) it 'shows a link if it exists' do
expect(presenter.author_name).to eq("<a href=\"#{user_path(audit_event.user)}\">#{audit_event.user.name}</a>")
end
it 'stores the name if it has been deleted' do
audit_event.user = nil
expect(presenter.author_name).to be_blank
end
end end
it 'exposes the target' do it 'exposes the target' do
...@@ -30,8 +40,16 @@ describe AuditEventPresenter do ...@@ -30,8 +40,16 @@ describe AuditEventPresenter do
expect(presenter.ip_address).to eq(details[:ip_address]) expect(presenter.ip_address).to eq(details[:ip_address])
end end
it 'exposes the object' do context 'exposes the object' do
expect(presenter.object).to eq(details[:entity_path]) it 'link if it exists' do
expect(presenter.object).to eq("<a href=\"#{url_for(audit_event.entity)}\">#{details[:entity_path]}</a>")
end
it 'stored name if it has been deleted' do
audit_event.entity_id = nil
expect(presenter.object).to be_blank
end
end end
it 'exposes the date' do it 'exposes the date' do
......
...@@ -182,27 +182,42 @@ describe API::Helpers do ...@@ -182,27 +182,42 @@ describe API::Helpers do
end end
describe "when authenticating using a job token" do describe "when authenticating using a job token" do
let(:job) { create(:ci_build, user: current_user) } let(:job) { create(:ci_build, user: user) }
let(:route_authentication_setting) { { job_token_allowed: true } }
before do context 'when route is allowed to be authenticated' do
allow_any_instance_of(described_class).to receive(:doorkeeper_guard).and_return(nil) let(:route_authentication_setting) { { job_token_allowed: true } }
end
it "returns nil for an invalid token" do it "returns a 401 response for an invalid token" do
env[API::APIGuard::JOB_TOKEN_HEADER] = 'invalid token' env[API::APIGuard::JOB_TOKEN_HEADER] = 'invalid token'
expect(current_user).to be_nil expect { current_user }.to raise_error /401/
end end
it "returns nil for a user without access" do it "returns a 403 response for a user without access" do
env[API::APIGuard::JOB_TOKEN_HEADER] = job.token env[API::APIGuard::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil expect { current_user }.to raise_error /403/
end
it 'returns a 403 response for a user who is blocked' do
user.block!
env[API::APIGuard::JOB_TOKEN_HEADER] = job.token
expect { current_user }.to raise_error /403/
end end
it "returns nil for a user with access, but route not allowed to be authenticated" do it "sets current_user" do
env[API::APIGuard::JOB_TOKEN_HEADER] = job.token
expect(current_user).to eq(user)
end
end
context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } }
it "sets current_user to nil" do
env[API::APIGuard::JOB_TOKEN_HEADER] = job.token env[API::APIGuard::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true) allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
...@@ -210,6 +225,7 @@ describe API::Helpers do ...@@ -210,6 +225,7 @@ describe API::Helpers do
end end
end end
end end
end
describe '.handle_api_exception' do describe '.handle_api_exception' do
before do before do
......
shared_examples_for 'audit event logging' do
before do
stub_licensed_features(extended_audit_events: true)
end
context 'if operation succeed' do
it 'logs an audit event if operation succeed' do
expect { operation }.to change(AuditEvent, :count).by(1)
end
it 'logs the project info' do
@resource = operation
expect(AuditEvent.last).to have_attributes(attributes)
end
end
it 'does not log audit event if project operation fails' do
fail_condition!
expect { operation }.not_to change(AuditEvent, :count)
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