[Rt-devel] RT::AuditLog preview
Todd Chapman
todd at chaka.net
Wed May 31 17:02:26 EDT 2006
In case I die hiking the Grand View Trail this weekend. :)
This sets up RT to created more transactions so you have
an audit history for things like changes to queue and
custom fields.
Just drop this in $RTHOME/local/lib/RT/
and in RT_SiteConfig.pm: "use RT::AuditLog;"
The user interface bits aren't really there yet, so it
might not be that interesting for most people.
Not for production use.
-Todd
-------------- next part --------------
use strict;
use warnings;
use RT::Transactions;
use RT::Date;
package RT::AuditLog;
use Storable qw(dclone);
my ( $old_value, @save_args );
my @SkipClasses = ( 'RT::Transaction', );
#my @SkipClasses = ( 'RT::Transaction', 'RT::CachedGroupMember', );
my %TransactionMap = (
'RT::CustomFieldValue' => {
Create => { ObjectId => sub { $_[0]->CustomField },
ObjectType => sub { 'RT::CustomField' },
Type => sub { 'AddValue' },
NewValue => sub { $_[0]->Name },
},
Delete => { ObjectId => sub { $_[0]->CustomField },
ObjectType => sub { 'RT::CustomField' },
Type => sub { 'DeleteValue' },
OldValue => sub { $_[0]->Name },
},
},
'RT::GroupMember' => {
Create => { ObjectId => sub { $_[0]->GroupId },
ObjectType => sub { 'RT::Group' },
Type => sub { 'AddMember' },
NewValue => sub { $_[0]->MemberId },
},
Delete => { ObjectId => sub { $_[0]->GroupId },
ObjectType => sub { 'RT::Group' },
Type => sub { 'DeleteMember' },
OldValue => sub { $_[0]->MemberId },
},
},
'RT::ACE' => {
Create => { ObjectId => sub { $_[0]->ObjectId },
ObjectType => sub { $_[0]->ObjectType },
Type => sub { 'GrantRight' },
NewValue => sub { $_[0]->RightName },
NewReference => sub { $_[0]->PrincipalId },
ReferenceType => sub { 'RT::' . $_[0]->PrincipalType },
},
Delete => { ObjectId => sub { $_[0]->ObjectId },
ObjectType => sub { $_[0]->ObjectType },
Type => sub { 'RevokeRight' },
OldValue => sub { $_[0]->RightName },
OldReference => sub { $_[0]->PrincipalId },
ReferenceType => sub { 'RT::' . $_[0]->PrincipalType },
},
},
'RT::CachedGroupMember' => {
Set => { ObjectId => sub { $_[0]->GroupId },
ObjectType => sub { my $p = RT::Principal->new( $_[0]->CurrentUser );
$p->Load($_[0]->GroupId);
return 'RT::' . $p->PrincipalType
},
Type => sub { 'Set' },
Field => sub { $_[1] },
NewValue => sub { $_[2] },
},
},
'RT::Scrip' => {
Create => { ObjectId => sub { $_[0]->Queue },
ObjectType => sub { 'RT::Queue' },
Type => sub { 'CreateScrip' },
NewValue => sub { $_[0]->Description },
NewReference => sub { $_[0]->id },
ReferenceType => sub { 'RT::Scrip' },
},
Delete => { ObjectId => sub { $_[0]->Queue },
ObjectType => sub { 'RT::Queue' },
Type => sub { 'DeleteScrip' },
OldValue => sub { $_[0]->Description },
OldReference => sub { $_[0]->id },
ReferenceType => sub { 'RT::Scrip' },
},
},
'RT::Template' => {
Create => { ObjectId => sub { $_[0]->Queue },
ObjectType => sub { 'RT::Queue' },
Type => sub { 'CreateTemplate' },
NewValue => sub { $_[0]->Name },
NewReference => sub { $_[0]->id },
ReferenceType => sub { 'RT::Template' },
},
Delete => { ObjectId => sub { $_[0]->Queue },
ObjectType => sub { 'RT::Queue' },
Type => sub { 'DeleteTemplate' },
OldValue => sub { $_[0]->Name },
OldReference => sub { $_[0]->id },
ReferenceType => sub { 'RT::Template' },
},
},
'RT::ObjectCustomField' => {
Create => { ObjectId => sub { $_[0]->ObjectId },
ObjectType => sub { my $type = $_[0]->CustomFieldObj->LookupType;
$type =~ /(RTx?::[^-]+)/;
return $1; },
Type => sub { 'ApplyCF' },
NewValue => sub { $_[0]->CustomFieldObj->Name },
NewReference => sub { $_[0]->CustomField },
ReferenceType => sub { 'RT::CustomField' },
},
Delete => { ObjectId => sub { $_[0]->ObjectId },
ObjectType => sub { my $type = $_[0]->CustomFieldObj->LookupType;
$type =~ /(RTx?::[^-]+)/;
return $1; },
Type => sub { 'RemoveCF' },
OldValue => sub { $_[0]->CustomFieldObj->Name },
OldReference => sub { $_[0]->CustomField },
ReferenceType => sub { 'RT::CustomField' },
},
},
);
sub PreSaveArgs {
@save_args = @_;
pop @save_args;
}
sub PreDelete {
$old_value = dclone $_[0];
@save_args = @_;
pop @save_args;
}
sub PreOldValue {
my $self = shift;
my $hook = pop;
my %args = @_;
$old_value = $self->__Value($args{Field});
}
sub PostSet {
# RT::Group records it's own transactions by default after calling Record::_Set
# so we do this to avoid recording duplicate transactions
return if ref($save_args[0]) eq 'RT::Group';
return if ref($save_args[0]) eq 'RT::User';
&_PostSet;
}
sub _PostSet {
@_ = ( @save_args, $_[-1] );
my $self = shift;
my $return = pop;
my %args = @_;
my $class = ref($self);
return if grep { $class eq $_ } @SkipClasses;
my $val = get_rv($return);
return unless $val;
return if DuplicateTransaction( ObjectType => $class, ObjectId => $self->id,
Type => 'Set', Field => $args{Field},
OldValue => $old_value, NewValue => $args{Value} );
# transaction on actual object
$self->_NewTransaction(
Type => 'Set',
Field => $args{Field},
OldValue => $old_value,
NewValue => $args{Value},
Data => 'AuditLog',
);
my $ref = $TransactionMap{$class}{Set} if exists $TransactionMap{$class};
$ref or return;
# transaction of interest. for example, if you apply a custom field to
# a queue, the CF gets the transaction above, but it is in the context
# of the quue that we are interested in seeing transactions on
my $trans = new RT::Transaction( $self->CurrentUser );
my ( $transaction, $msg ) = $trans->Create(
ObjectId => $ref->{ObjectId}->($self, $args{Field}, $args{Value}),
ObjectType => $ref->{ObjectType}->($self, $args{Field}, $args{Value}),
Type => $ref->{Type}->($self, $args{Field}, $args{Value}),
NewValue => $ref->{NewValue}->($self, $args{Field}, $args{Value}),
Data => 'AuditLog',
exists $ref->{NewReference} ? ( NewReference => $ref->{NewReference}->($self, $args{Field}, $args{Value}),
ReferenceType => $ref->{ReferenceType}->($self, $args{Field}, $args{Value}) )
: (),
exists $ref->{Field} ? ( Field => $ref->{Field}->($self, $args{Field}, $args{Value}) ) : (),
OldValue => $old_value,
);
}
sub DuplicateTransaction {
my %args = @_;
my $seconds_back = delete $args{SecondsAgo} || 60;
my $date = RT::Date->new( $RT::SystemUser );
$date->SetToNow;
$date->AddSeconds( -$seconds_back );
# This makes sure some other part of RT hasn't already recorded this
# transaction. This need to be smarted because if a field is set, say
# from A to B, then from B to A, then from A to B again, only one
# A to B transaction will be recorded. We should maybe check the time
# on the previous transaction, and if it is more than some number of
# seconds old, go ahead and record it again.
my $Transactions = RT::Transactions->new( $RT::SystemUser );
while ( my ( $key, $value ) = each %args ) {
$Transactions->Limit( FIELD => $key, VALUE => $value );
}
$Transactions->Limit( FIELD => 'Created', VALUE => $date->ISO, OPERATOR => '>' );
return $Transactions->Count;
}
sub PostGroupSet {
# It's a matter of timing. By calling _PostSet after Group::_Set instead
# of Record::_Set, _PostSet can check to make sure it doesn't record a
# duplicate transaction
&_PostSet;
}
sub PostCreate {
@_ = ( @save_args, $_[-1] );
my $self = shift;
my $return = pop;
my %args = @_;
my $class = ref($self);
return if grep { $class eq $_ } @SkipClasses;
my $val = get_rv($return);
return unless $val;
return if
DuplicateTransaction( ObjectType => $class, ObjectId => $self->id, Type => 'Create' );
# transaction on actual object
$self->_NewTransaction(
Type => 'Create',
Data => 'AuditLog',
$self->can('Name') ? ( NewValue => $self->Name ) : (),
);
my $ref = $TransactionMap{$class}{Create} if exists $TransactionMap{$class};
$ref or return;
# transaction of interest. for example, if you apply a custom field to
# a queue, the CF gets the transaction above, but it is in the context
# of the quue that we are interested in seeing transactions on
my $trans = new RT::Transaction( $self->CurrentUser );
my ( $transaction, $msg ) = $trans->Create(
ObjectId => $ref->{ObjectId}->($self),
ObjectType => $ref->{ObjectType}->($self),
Type => $ref->{Type}->($self),
NewValue => $ref->{NewValue}->($self),
Data => 'AuditLog',
exists $ref->{NewReference} ? ( NewReference => $ref->{NewReference}->($self),
ReferenceType => $ref->{ReferenceType}->($self) )
: (),
);
}
sub PostDelete {
@_ = ( @save_args, $_[-1] );
my $self = shift;
my $return = pop;
my %args = @_;
my $class = ref($self);
return if grep { $class eq $_ } @SkipClasses;
my $val = get_rv($return);
return unless $val;
return if
DuplicateTransaction( ObjectType => $class, ObjectId => $old_value->id, Type => 'Delete' );
$old_value->_NewTransaction(
Type => 'Delete',
Data => 'AuditLog',
$self->can('Name') ? ( OldValue => $self->Name ) : (),
);
my $ref = $TransactionMap{$class}{Delete} if exists $TransactionMap{$class};
$ref or return;
my $trans = new RT::Transaction( $self->CurrentUser );
my ( $transaction, $msg ) = $trans->Create(
ObjectId => $ref->{ObjectId}->($self),
ObjectType => $ref->{ObjectType}->($self),
Type => $ref->{Type}->($self),
OldValue => $ref->{OldValue}->($self),
Data => 'AuditLog',
exists $ref->{OldReference} ? ( OldReference => $ref->{OldReference}->($self),
ReferenceType => $ref->{ReferenceType}->($self) )
: (),
);
}
sub get_rv {
my $return = shift;
my $val;
if ( ref($return) eq 'ARRAY' ) {
$val = $return->[0];
}
elsif ( ref($return) eq 'Class::ReturnValue' ) {
$val = ($return->as_array)[0];
}
else {
$val = $return;
}
return $val;
}
package RT::Scrip;
use Hook::LexWrap;
wrap '_Set' => pre => \&RT::AuditLog::PreSaveArgs;
wrap '_Set' => pre => \&RT::AuditLog::PreOldValue,
post => \&RT::AuditLog::PostSet;
package RT::Record;
use Hook::LexWrap;
wrap '_Set' => pre => \&RT::AuditLog::PreSaveArgs;
wrap '_Set' => pre => \&RT::AuditLog::PreOldValue,
post => \&RT::AuditLog::PostSet;
wrap 'Create' => pre => \&RT::AuditLog::PreSaveArgs,
post => \&RT::AuditLog::PostCreate;
wrap 'Delete' => pre => \&RT::AuditLog::PreDelete,
post => \&RT::AuditLog::PostDelete;
package RT::User;
use Hook::LexWrap;
wrap '_Set' => pre => \&RT::AuditLog::PreSaveArgs;
wrap '_Set' => pre => \&RT::AuditLog::PreOldValue,
post => \&RT::AuditLog::PostGroupSet;
package RT::Group;
use Hook::LexWrap;
wrap '_Set' => pre => \&RT::AuditLog::PreSaveArgs;
wrap '_Set' => pre => \&RT::AuditLog::PreOldValue,
post => \&RT::AuditLog::PostGroupSet;
sub Content { "need to create _BriefDescriptions" }
package RT::Transaction;
$_BriefDescriptions{AddMember} =
sub {
my $self = shift;
my $principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $self->__Value('NewValue') );
my $object = $principal->Object;
my $type = $principal->IsUser ? 'user' : 'group';
return $self->loc("Member added: [_1] '[_2]'", $type, $object->Name);
};
$_BriefDescriptions{DeleteMember} =
sub {
my $self = shift;
my $principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $self->__Value('OldValue') );
my $object = $principal->Object;
my $type = $principal->IsUser ? 'user' : 'group';
return $self->loc("Member deleted: [_1] '[_2]'", $type, $object->Name);
};
$_BriefDescriptions{GrantRight} =
sub {
my $self = shift;
my $principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $self->NewReference );
my $object = $principal->Object;
if ( ref($object) eq 'RT::Group' and $object->Type eq 'UserEquiv' ) {
$principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $object->Instance );
$object = $principal->Object;
}
if ( $principal->IsUser ) {
return $self->loc("Right [_1] granted to [_2] '[_3]'", $self->__Value('NewValue'), 'user', $object->Name);
}
else {
return $self->loc("Right [_1] granted to [_2] '[_3]'", $self->__Value('NewValue'), 'group', $object->Name || $object->Type);
}
};
$_BriefDescriptions{RevokeRight} =
sub {
my $self = shift;
my $principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $self->OldReference );
my $object = $principal->Object;
if ( ref($object) eq 'RT::Group' and $object->Type eq 'UserEquiv' ) {
$principal = RT::Principal->new( $self->CurrentUser );
$principal->Load( $object->Instance );
$object = $principal->Object;
}
if ( $principal->IsUser ) {
return $self->loc("Right [_1] revoked from [_2] '[_3]'", $self->__Value('NewValue'), 'user', $object->Name);
}
else {
return $self->loc("Right [_1] revoked from [_2] '[_3]'", $self->__Value('NewValue'), 'group', $object->Name || $object->Type);
}
};
1;
More information about the Rt-devel
mailing list