Commit ac614ea1 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'audit_events' into 'master'

Audit feature / permission changes in log

Audit events for membership changes of the group and project.

See merge request !250
parents 991204ea 4eaeb3b6
class AuditEventsController < ApplicationController
# Authorize
before_filter :repository, only: :project_log
before_filter :authorize_admin_project!, only: :project_log
before_filter :group, only: :group_log
before_filter :authorize_admin_group!, only: :group_log
layout :determine_layout
def project_log
@events = AuditEvent.where(entity_type: "Project", entity_id: project.id).page(params[:page]).per(20)
end
def group_log
@events = AuditEvent.where(entity_type: "Group", entity_id: group.id).page(params[:page]).per(20)
end
private
def group
@group ||= Group.find_by(path: params[:group_id])
end
def authorize_admin_group!
render_404 unless can?(current_user, :manage_group, group)
end
def determine_layout
if @project
'project_settings'
elsif @group
'group'
end
end
def audit_events_params
params.permit(:project_id, :group_id)
end
end
......@@ -7,21 +7,59 @@ class Groups::GroupMembersController < ApplicationController
layout 'group'
def create
@group.add_users(params[:user_ids].split(','), params[:access_level])
access_level = params[:access_level]
user_ids = params[:user_ids].split(',')
@group.add_users(user_ids, access_level)
users = User.where(id: user_ids).pluck(:id, :name)
users.each do |user|
details = {
add: "user_access",
as: Gitlab::Access.options_with_owner.key(access_level.to_i),
target_id: user[0],
target_type: "User",
target_details: user[1],
}
AuditEventService.new(current_user, @group, details).security_event
end
redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end
def update
@member = @group.group_members.find(params[:id])
@member.update_attributes(member_params)
old_access_level = @member.human_access
if @member.update_attributes(member_params)
details = {
change: "access_level",
from: old_access_level,
to: @member.human_access,
target_id: @member.user_id,
target_type: "User",
target_details: @member.user.name,
}
AuditEventService.new(current_user, @group, details).security_event
end
end
def destroy
@users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
user_id = @users_group.id
user_name = @users_group.user.name
if @users_group.destroy
details = {
remove: "user_access",
target_id: user_id,
target_type: "User",
target_details: user_name,
}
AuditEventService.new(current_user, @group, details).security_event
end
respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
......
......@@ -16,8 +16,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:access_level]]
access_level = params[:access_level]
@project.team << [users, access_level]
users.each do |user|
details = {
add: "user_access",
as: Gitlab::Access.options_with_owner.key(access_level.to_i),
target_id: user.id,
target_type: "User",
target_details: user.name,
}
AuditEventService.new(current_user, @project, details).security_event
end
if params[:redirect_to]
redirect_to params[:redirect_to]
......@@ -28,7 +39,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def update
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
old_access_level = @user_project_relation.human_access
if @user_project_relation.update_attributes(member_params)
details = {
change: "access_level",
from: old_access_level,
to: @user_project_relation.human_access,
target_id: @user_project_relation.user_id,
target_type: "User",
target_details: @user_project_relation.user.name,
}
AuditEventService.new(current_user, @project, details).security_event
end
unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role"
......@@ -38,7 +61,18 @@ class Projects::TeamMembersController < Projects::ApplicationController
def destroy
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy
user_id = @user_project_relation.user_id
user_name = @user_project_relation.user.name
if @user_project_relation.destroy
details = {
remove: "user_access",
target_id: user_id,
target_type: "User",
target_details: user_name,
}
AuditEventService.new(current_user, @project, details).security_event
end
respond_to do |format|
format.html { redirect_to project_team_index_path(@project) }
......
module AuditEventsHelper
def human_text(details)
details.map{ |key, value| select_keys(key, value) }.join(" ").humanize
end
def select_keys(key, value)
if key.match(/^target_.*/)
""
else
"#{key.to_s} <strong>#{value}</strong>"
end
end
end
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
belongs_to :user, foreign_key: :author_id
validates :author_id, presence: true
validates :entity_id, presence: true
validates :entity_type, presence: true
after_initialize :initialize_details
def initialize_details
self.details = {} if details.nil?
end
def author_name
self.user.name
end
end
class SecurityEvent < AuditEvent
end
class AuditEventService
def initialize(author, entity, details = {})
@author, @entity, @details = author, entity, details
end
def security_event
SecurityEvent.create(
author_id: @author.id,
entity_id: @entity.id,
entity_type: @entity.class.name,
details: @details
)
end
end
- if defined?(events)
%table.table#audits
%thead
%tr
%th
%th
%th Author
%th
%th
%th Action
%th
%th Target
%th
%th At
%tbody
- events.each do |event|
%tr
%td
%td
%td #{event.author_name}
%td
%td
%td #{raw human_text(event.details)}
%td
%td #{event.details[:target_details]}
%td
%td #{event.created_at}
= paginate events
.row
.col-md-2
= render 'groups/settings_nav'
.col-md-10
%h3.page-title Group Audit Events
%p.light Events in #{@group.name}
= render 'event_table', events: @events
%h3.page-title Project Audit Events
%p.light Events in #{@project.path_with_namespace}
= render 'event_table', events: @events
......@@ -11,4 +11,8 @@
= link_to group_ldap_group_links_path(@group) do
%i.icon-exchange
LDAP Groups
= nav_link(controller: :audit_events) do
= link_to group_audit_events_path(@group) do
%i.fa.fa-file-text-o
Audit Events
......@@ -31,3 +31,7 @@
= link_to project_protected_branches_path(@project) do
%i.fa.fa-lock
Protected branches
= nav_link(controller: :audit_events) do
= link_to project_audit_events_path(@project) do
%i.fa.fa-file-text-o
Audit Events
......@@ -184,6 +184,8 @@ Gitlab::Application.routes.draw do
resource :avatar, only: [:destroy]
resources :milestones
end
get "/audit_events" => "audit_events#group_log"
end
get 'unsubscribes/:email', to: 'unsubscribes#show', as: :unsubscribe
......@@ -358,6 +360,8 @@ Gitlab::Application.routes.draw do
end
end
end
get "/audit_events" => "audit_events#project_log"
end
get ':id' => "namespaces#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
......
class AddAuditEvent < ActiveRecord::Migration
def change
create_table :audit_events do |t|
t.integer :author_id, null: false
t.string :type, null: false
# "Namespace" where the change occurs
# eg. On a project, group or user
t.integer :entity_id, null: false
t.string :entity_type, null: false
# Details for the event
t.text :details
t.timestamps
end
add_index :audit_events, :author_id
add_index :audit_events, :type
add_index :audit_events, [:entity_id, :entity_type]
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141103160516) do
ActiveRecord::Schema.define(version: 20141118150935) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -25,6 +25,20 @@ ActiveRecord::Schema.define(version: 20141103160516) do
t.datetime "updated_at"
end
create_table "audit_events", force: true do |t|
t.integer "author_id", null: false
t.string "type", null: false
t.integer "entity_id", null: false
t.string "entity_type", null: false
t.text "details"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "audit_events", ["author_id"], name: "index_audit_events_on_author_id", using: :btree
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
......
......@@ -146,3 +146,14 @@ Feature: Groups
And I click on one group milestone
Then I should see group milestone with descriptions and expiry date
And I should see group milestone with all issues and MRs assigned to that milestone
@javascript
Scenario: I should see audit events
Given User "Mary Jane" exists
When I visit group "Owned" members page
And I select user "Mary Jane" from list with role "Reporter"
And I change the role to "Developer"
And I click on the "Remove User From Group" button for "Mary Jane"
When I visit group "Owned" settings page
And I go to "Audit Events"
Then I should see the audit event listed
......@@ -49,3 +49,14 @@ Feature: Project
Then I should see project "Forum" README
And I visit project "Shop" page
Then I should see project "Shop" README
@javascript
Scenario: I should see audit events
And gitlab user "Pete"
And "Pete" is "Shop" developer
When I visit project "Shop" settings page
And I go to "Members"
And I change "Pete" access level to master
When I visit project "Shop" settings page
And I go to "Audit Events"
Then I should see the audit event listed
......@@ -57,6 +57,33 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
projects_with_access.should_not have_content("Mary Jane")
end
step 'I change the role to "Developer"' do
user = User.find_by(name: "Mary Jane")
member = Group.find_by(name: "Owned").members.where(user_id: user.id).first
within "#group_member_#{member.id}" do
find(".btn-tiny.btn.js-toggle-button").click
within "#edit_group_member_#{member.id}" do
select 'Developer', from: 'group_member_access_level'
click_on 'Save'
end
end
end
step 'I go to "Audit Events"' do
click_link 'Audit Events'
end
step 'I should see the audit event listed' do
within ('table#audits') do
page.should have_content 'Add user access as reporter'
page.should have_content 'Change access level from reporter to developer'
page.should have_content 'Remove user access'
page.should have_content('John Doe', count: 3)
page.should have_content('Mary Jane', count: 3)
end
end
step 'project from group "Owned" has issues assigned to me' do
create :issue,
project: project,
......
......@@ -2,6 +2,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include Select2Helper
step 'change project settings' do
fill_in 'project_name_edit', with: 'NewName'
......@@ -69,4 +70,41 @@ class Spinach::Features::Project < Spinach::FeatureSteps
page.should have_link "README.md"
page.should have_content "testme"
end
step 'gitlab user "Pete"' do
create(:user, name: "Pete")
end
step '"Pete" is "Shop" developer' do
user = User.find_by(name: "Pete")
project = Project.find_by(name: "Shop")
project.team << [user, :developer]
end
step 'I visit project "Shop" settings page' do
click_link 'Settings'
end
step 'I go to "Members"' do
click_link 'Members'
end
step 'I change "Pete" access level to master' do
user = User.find_by(name: "Pete")
within "#user_#{user.id}" do
select "Master", from: "project_member_access_level"
end
end
step 'I go to "Audit Events"' do
click_link 'Audit Events'
end
step 'I should see the audit event listed' do
within ('table#audits') do
page.should have_content "Change access level from developer to master"
page.should have_content(project.owner.name)
page.should have_content('Pete')
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