Commit 8782cbb1 authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 622943: Simple auditing of changes to Bugzilla::Object subclass objects

r=dkl, a=mkanat
parent 317fc6d9
......@@ -71,6 +71,8 @@ use base qw(Bugzilla::Object);
use constant DB_TABLE => 'attachments';
use constant ID_FIELD => 'attach_id';
use constant LIST_ORDER => ID_FIELD;
# Attachments are tracked in bugs_activity.
use constant AUDIT_UPDATES => 0;
sub DB_COLUMNS {
my $dbh = Bugzilla->dbh;
......
......@@ -73,6 +73,8 @@ use constant DB_TABLE => 'bugs';
use constant ID_FIELD => 'bug_id';
use constant NAME_FIELD => 'alias';
use constant LIST_ORDER => ID_FIELD;
# Bugs have their own auditing table, bugs_activity.
use constant AUDIT_UPDATES => 0;
# This is a sub because it needs to call other subroutines.
sub DB_COLUMNS {
......
......@@ -37,6 +37,9 @@ use Scalar::Util qw(blessed);
#### Initialization ####
###############################
# Updates of comments are audited in bugs_activity instead of audit_log.
use constant AUDIT_UPDATES => 0;
use constant DB_COLUMNS => qw(
comment_id
bug_id
......
......@@ -189,6 +189,9 @@ use Memoize;
PRIVILEGES_REQUIRED_REPORTER
PRIVILEGES_REQUIRED_ASSIGNEE
PRIVILEGES_REQUIRED_EMPOWERED
AUDIT_CREATE
AUDIT_REMOVE
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
......@@ -575,6 +578,11 @@ use constant PRIVILEGES_REQUIRED_REPORTER => 1;
use constant PRIVILEGES_REQUIRED_ASSIGNEE => 2;
use constant PRIVILEGES_REQUIRED_EMPOWERED => 3;
# Special field values used in the audit_log table to mean either
# "we just created this object" or "we just deleted this object".
use constant AUDIT_CREATE => '__create__';
use constant AUDIT_REMOVE => '__remove__';
sub bz_locations {
# We know that Bugzilla/Constants.pm must be in %INC at this point.
# So the only question is, what's the name of the directory
......
......@@ -507,6 +507,23 @@ use constant ABSTRACT_SCHEMA => {
],
},
# Auditing
# --------
audit_log => {
FIELDS => [
user_id => {TYPE => 'INT3',
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
class => {TYPE => 'varchar(255)', NOTNULL => 1},
object_id => {TYPE => 'INT4', NOTNULL => 1},
field => {TYPE => 'varchar(64)', NOTNULL => 1},
removed => {TYPE => 'MEDIUMTEXT'},
added => {TYPE => 'MEDIUMTEXT'},
at_time => {TYPE => 'DATETIME', NOTNULL => 1},
],
},
# Keywords
# --------
......
......@@ -74,6 +74,8 @@ use base qw(Bugzilla::Object Exporter);
use constant DB_TABLE => 'flags';
use constant LIST_ORDER => 'id';
# Flags are tracked in bugs_activity.
use constant AUDIT_UPDATES => 0;
use constant SKIP_REQUESTEE_ON_ERROR => 1;
......
......@@ -43,6 +43,7 @@ use constant VALIDATOR_DEPENDENCIES => {};
# XXX At some point, this will be joined with FIELD_MAP.
use constant REQUIRED_FIELD_MAP => {};
use constant EXTRA_REQUIRED_FIELDS => ();
use constant AUDIT_UPDATES => 1;
# This allows the JSON-RPC interface to return Bugzilla::Object instances
# as though they were hashes. In the future, this may be modified to return
......@@ -392,6 +393,8 @@ sub update {
{ object => $self, old_object => $old_self,
changes => \%changes });
$self->audit_log(\%changes) if $self->AUDIT_UPDATES;
$dbh->bz_commit_transaction();
if (wantarray) {
......@@ -406,11 +409,43 @@ sub remove_from_db {
Bugzilla::Hook::process('object_before_delete', { object => $self });
my $table = $self->DB_TABLE;
my $id_field = $self->ID_FIELD;
Bugzilla->dbh->do("DELETE FROM $table WHERE $id_field = ?",
undef, $self->id);
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
$self->audit_log(AUDIT_REMOVE);
$dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
$dbh->bz_commit_transaction();
undef $self;
}
sub audit_log {
my ($self, $changes) = @_;
my $class = ref $self;
my $dbh = Bugzilla->dbh;
my $user_id = Bugzilla->user->id || undef;
my $sth = $dbh->prepare(
'INSERT INTO audit_log (user_id, class, object_id, field,
removed, added, at_time)
VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))');
# During creation or removal, $changes is actually just a string
# indicating whether we're creating or removing the object.
if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
# We put the object's name in the "added" or "removed" field.
# We do this thing with NAME_FIELD because $self->name returns
# the wrong thing for Bugzilla::User.
my $name = $self->{$self->NAME_FIELD};
my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name)
: ($name, undef);
$sth->execute($user_id, $class, $self->id, $changes, @added_removed);
return;
}
# During update, it's the actual %changes hash produced by update().
foreach my $field (keys %$changes) {
my ($from, $to) = @{ $changes->{$field} };
$sth->execute($user_id, $class, $self->id, $field, $from, $to);
}
}
###############################
#### Subroutines ######
###############################
......@@ -522,6 +557,8 @@ sub insert_create_data {
Bugzilla::Hook::process('object_end_of_create', { class => $class,
object => $object });
$object->audit_log(AUDIT_CREATE);
return $object;
}
......
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