summaryrefslogtreecommitdiff
path: root/rt/lib/RT/ACE.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/ACE.pm')
-rwxr-xr-xrt/lib/RT/ACE.pm649
1 files changed, 545 insertions, 104 deletions
diff --git a/rt/lib/RT/ACE.pm b/rt/lib/RT/ACE.pm
index dca50c359..ae3eda42b 100755
--- a/rt/lib/RT/ACE.pm
+++ b/rt/lib/RT/ACE.pm
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -46,88 +46,548 @@
#
# END BPS TAGGED BLOCK }}}
-# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
-# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
-#
-# !! DO NOT EDIT THIS FILE !!
-#
-
-use strict;
+=head1 SYNOPSIS
+ use RT::ACE;
+ my $ace = RT::ACE->new($CurrentUser);
-=head1 NAME
-RT::ACE
+=head1 DESCRIPTION
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
=head1 METHODS
+
=cut
+
package RT::ACE;
use base 'RT::Record';
-sub _Init {
- my $self = shift;
+sub Table {'ACL'}
- $self->Table('ACL');
- $self->SUPER::_Init(@_);
-}
+use strict;
+use warnings;
+use RT::Principals;
+use RT::Queues;
+use RT::Groups;
+use vars qw (
+ %LOWERCASERIGHTNAMES
+ %OBJECT_TYPES
+ %TICKET_METAPRINCIPALS
+);
-=head2 Create PARAMHASH
-Create takes a hash of values and creates a row in the database:
+=head1 Rights
- varchar(25) 'PrincipalType'.
- int(11) 'PrincipalId'.
- varchar(25) 'RightName'.
- varchar(25) 'ObjectType'.
- int(11) 'ObjectId'.
- int(11) 'DelegatedBy'.
- int(11) 'DelegatedFrom'.
+# Queue rights are the sort of queue rights that can only be granted
+# to real people or groups
=cut
+
+
+%TICKET_METAPRINCIPALS = (
+ Owner => 'The owner of a ticket', # loc_pair
+ Requestor => 'The requestor of a ticket', # loc_pair
+ Cc => 'The CC of a ticket', # loc_pair
+ AdminCc => 'The administrative CC of a ticket', # loc_pair
+);
+
+
+
+
+=head2 LoadByValues PARAMHASH
+
+Load an ACE by specifying a paramhash with the following fields:
+
+ PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+
+ And either:
+
+ Object => undef,
+
+ OR
+
+ ObjectType => undef,
+ ObjectId => undef
+
+=cut
+
+sub LoadByValues {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+ Object => undef,
+ ObjectId => undef,
+ ObjectType => undef,
+ @_ );
+
+ if ( $args{'RightName'} ) {
+ my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+ unless ( $canonic_name ) {
+ return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
+ }
+ $args{'RightName'} = $canonic_name;
+ }
+
+ my $princ_obj;
+ ( $princ_obj, $args{'PrincipalType'} ) =
+ $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+ $args{'PrincipalType'} );
+
+ unless ( $princ_obj->id ) {
+ return ( 0,
+ $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+ );
+ }
+
+ my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args );
+ unless( $object ) {
+ return ( 0, $self->loc("System error. Right not granted.") );
+ }
+
+ $self->LoadByCols( PrincipalId => $princ_obj->Id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => $object_type,
+ ObjectId => $object_id);
+
+ #If we couldn't load it.
+ unless ( $self->Id ) {
+ return ( 0, $self->loc("ACE not found") );
+ }
+
+ # if we could
+ return ( $self->Id, $self->loc("Right Loaded") );
+
+}
+
+
+
+=head2 Create <PARAMS>
+
+PARAMS is a parameter hash with the following elements:
+
+ PrincipalId => The id of an RT::Principal object
+ PrincipalType => "User" "Group" or any Role type
+ RightName => the name of a right. in any case
+
+
+ Either:
+
+ Object => An object to create rights for. ususally, an RT::Queue or RT::Group
+ This should always be a DBIx::SearchBuilder::Record subclass
+
+ OR
+
+ ObjectType => the type of the object in question (ref ($object))
+ ObjectId => the id of the object in question $object->Id
+
+
+
+ Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false.
+
+
+
+=cut
+
sub Create {
my $self = shift;
- my %args = (
- PrincipalType => '',
- PrincipalId => '0',
- RightName => '',
- ObjectType => '',
- ObjectId => '0',
- DelegatedBy => '0',
- DelegatedFrom => '0',
-
- @_);
- $self->SUPER::Create(
- PrincipalType => $args{'PrincipalType'},
- PrincipalId => $args{'PrincipalId'},
- RightName => $args{'RightName'},
- ObjectType => $args{'ObjectType'},
- ObjectId => $args{'ObjectId'},
- DelegatedBy => $args{'DelegatedBy'},
- DelegatedFrom => $args{'DelegatedFrom'},
-);
+ my %args = (
+ PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+ Object => undef,
+ @_
+ );
+
+ unless ( $args{'RightName'} ) {
+ return ( 0, $self->loc('No right specified') );
+ }
+
+ #if we haven't specified any sort of right, we're talking about a global right
+ if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
+ $args{'Object'} = $RT::System;
+ }
+ ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args );
+ unless( $args{'Object'} ) {
+ return ( 0, $self->loc("System error. Right not granted.") );
+ }
+
+ # Validate the principal
+ my $princ_obj;
+ ( $princ_obj, $args{'PrincipalType'} ) =
+ $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+ $args{'PrincipalType'} );
+
+ unless ( $princ_obj->id ) {
+ return ( 0,
+ $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+ );
+ }
+
+ # }}}
+
+ # Check the ACL
+
+ if (ref( $args{'Object'}) eq 'RT::Group' ) {
+ unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
+ Right => 'AdminGroup' )
+ ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+
+ else {
+ unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ # }}}
+
+ # Canonicalize and check the right name
+ my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+ unless ( $canonic_name ) {
+ return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
+ }
+ $args{'RightName'} = $canonic_name;
+
+ #check if it's a valid RightName
+ if ( $args{'Object'}->can('AvailableRights') ) {
+ my $available = $args{'Object'}->AvailableRights;
+ unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
+ $RT::Logger->warning(
+ "Couldn't validate right name '$args{'RightName'}'"
+ ." for object of ". ref( $args{'Object'} ) ." class"
+ );
+ return ( 0, $self->loc('Invalid right') );
+ }
+ }
+ # }}}
+
+ # Make sure the right doesn't already exist.
+ $self->LoadByCols( PrincipalId => $princ_obj->id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => $args{'ObjectType'},
+ ObjectId => $args{'ObjectId'},
+ );
+ if ( $self->Id ) {
+ return ( 0, $self->loc('That principal already has that right') );
+ }
+
+ my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => ref( $args{'Object'} ),
+ ObjectId => $args{'Object'}->id,
+ );
+
+ #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
+ RT::Principal->InvalidateACLCache();
+
+ if ( $id ) {
+ return ( $id, $self->loc('Right Granted') );
+ }
+ else {
+ return ( 0, $self->loc('System error. Right not granted.') );
+ }
+}
+
+
+
+=head2 Delete { InsideTransaction => undef}
+
+Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
+If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
+Really, DBIx::SearchBuilder should use and/or fake subtransactions
+
+This routine will also recurse and delete any delegations of this right
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ( $self->Id ) {
+ return ( 0, $self->loc('Right not loaded.') );
+ }
+
+ # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
+ # or if it's a delegated ACE and it was delegated by the current user
+ unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ $self->_Delete(@_);
+}
+
+# Helper for Delete with no ACL check
+sub _Delete {
+ my $self = shift;
+ my %args = ( InsideTransaction => undef,
+ @_ );
+
+ my $InsideTransaction = $args{'InsideTransaction'};
+
+ $RT::Handle->BeginTransaction() unless $InsideTransaction;
+
+ my ( $val, $msg ) = $self->SUPER::Delete(@_);
+
+ if ($val) {
+ #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
+ # TODO what about the groups key cache?
+ RT::Principal->InvalidateACLCache();
+ $RT::Handle->Commit() unless $InsideTransaction;
+ return ( $val, $self->loc('Right revoked') );
+ }
+
+ $RT::Handle->Rollback() unless $InsideTransaction;
+ return ( 0, $self->loc('Right could not be revoked') );
+}
+
+
+
+=head2 _BootstrapCreate
+
+Grant a right with no error checking and no ACL. this is _only_ for
+installation. If you use this routine without the author's explicit
+written approval, he will hunt you down and make you spend eternity
+translating mozilla's code into FORTRAN or intercal.
+
+If you think you need this routine, you've mistaken.
+
+=cut
+
+sub _BootstrapCreate {
+ my $self = shift;
+ my %args = (@_);
+
+ # When bootstrapping, make sure we get the _right_ users
+ if ( $args{'UserId'} ) {
+ my $user = RT::User->new( $self->CurrentUser );
+ $user->Load( $args{'UserId'} );
+ delete $args{'UserId'};
+ $args{'PrincipalId'} = $user->PrincipalId;
+ $args{'PrincipalType'} = 'User';
+ }
+
+ my $id = $self->SUPER::Create(%args);
+
+ if ( $id > 0 ) {
+ return ($id);
+ }
+ else {
+ $RT::Logger->err('System error. right not granted.');
+ return (undef);
+ }
+
+}
+
+
+sub RightName {
+ my $self = shift;
+ my $val = $self->_Value('RightName');
+ return $val unless $val;
+
+ my $available = $self->Object->AvailableRights;
+ foreach my $right ( keys %$available ) {
+ return $right if $val eq $self->CanonicalizeRightName($right);
+ }
+
+ $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
+ return $val;
}
+=head2 CanonicalizeRightName <RIGHT>
+
+Takes a queue or system right name in any case and returns it in
+the correct case. If it's not found, will return undef.
+
+=cut
+
+sub CanonicalizeRightName {
+ my $self = shift;
+ return $LOWERCASERIGHTNAMES{ lc shift };
+}
+
+
+
+
+=head2 Object
+
+If the object this ACE applies to is a queue, returns the queue object.
+If the object this ACE applies to is a group, returns the group object.
+If it's the system object, returns undef.
+
+If the user has no rights, returns undef.
+
+=cut
+
+
+
+
+sub Object {
+ my $self = shift;
+
+ my $appliesto_obj;
+
+ if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
+ $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser);
+ unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
+ return undef;
+ }
+ $appliesto_obj->Load( $self->__Value('ObjectId') );
+ return ($appliesto_obj);
+ }
+ else {
+ $RT::Logger->warning( "$self -> Object called for an object "
+ . "of an unknown type:"
+ . $self->__Value('ObjectType') );
+ return (undef);
+ }
+}
+
+
+
+=head2 PrincipalObj
+
+Returns the RT::Principal object for this ACE.
+
+=cut
+
+sub PrincipalObj {
+ my $self = shift;
+
+ my $princ_obj = RT::Principal->new( $self->CurrentUser );
+ $princ_obj->Load( $self->__Value('PrincipalId') );
+
+ unless ( $princ_obj->Id ) {
+ $RT::Logger->err(
+ "ACE " . $self->Id . " couldn't load its principal object" );
+ }
+ return ($princ_obj);
+
+}
+
+
+
+
+sub _Set {
+ my $self = shift;
+ return ( 0, $self->loc("ACEs can only be created and deleted.") );
+}
+
+
+
+sub _Value {
+ my $self = shift;
+
+ if ( $self->PrincipalObj->IsGroup
+ && $self->PrincipalObj->Object->HasMemberRecursively(
+ $self->CurrentUser->PrincipalObj
+ )
+ ) {
+ return ( $self->__Value(@_) );
+ }
+ elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
+ return ( $self->__Value(@_) );
+ }
+ else {
+ return undef;
+ }
+}
+
+
+
+
+
+=head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
+
+Takes a principal id and a principal type.
+
+If the principal is a user, resolves it to the proper acl equivalence group.
+Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with
+
+=cut
+
+sub _CanonicalizePrincipal {
+ my $self = shift;
+ my $princ_id = shift;
+ my $princ_type = shift || '';
+
+ my $princ_obj = RT::Principal->new(RT->SystemUser);
+ $princ_obj->Load($princ_id);
+
+ unless ( $princ_obj->Id ) {
+ use Carp;
+ $RT::Logger->crit(Carp::longmess);
+ $RT::Logger->crit("Can't load a principal for id $princ_id");
+ return ( $princ_obj, undef );
+ }
+
+ # Rights never get granted to users. they get granted to their
+ # ACL equivalence groups
+ if ( $princ_type eq 'User' ) {
+ my $equiv_group = RT::Group->new( $self->CurrentUser );
+ $equiv_group->LoadACLEquivalenceGroup($princ_obj);
+ unless ( $equiv_group->Id ) {
+ $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
+ return ( RT::Principal->new(RT->SystemUser), undef );
+ }
+ $princ_obj = $equiv_group->PrincipalObj();
+ $princ_type = 'Group';
+
+ }
+ return ( $princ_obj, $princ_type );
+}
+
+sub _ParseObjectArg {
+ my $self = shift;
+ my %args = ( Object => undef,
+ ObjectId => undef,
+ ObjectType => undef,
+ @_ );
+
+ if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
+ $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
+ return ();
+ } elsif( $args{'Object'} && ref($args{'Object'}) && !$args{'Object'}->can('id') ) {
+ $RT::Logger->crit( "Method called called Object that has no id method" );
+ return ();
+ } elsif( $args{'Object'} ) {
+ my $obj = $args{'Object'};
+ return ($obj, ref $obj, $obj->id);
+ } elsif ( $args{'ObjectType'} ) {
+ my $obj = $args{'ObjectType'}->new( $self->CurrentUser );
+ $obj->Load( $args{'ObjectId'} );
+ return ($obj, ref $obj, $obj->id);
+ } else {
+ $RT::Logger->crit( "Method called with wrong args" );
+ return ();
+ }
+}
+
+
+# }}}
+
=head2 id
-Returns the current value of id.
+Returns the current value of id.
(In the database, id is stored as int(11).)
@@ -136,7 +596,7 @@ Returns the current value of id.
=head2 PrincipalType
-Returns the current value of PrincipalType.
+Returns the current value of PrincipalType.
(In the database, PrincipalType is stored as varchar(25).)
@@ -144,7 +604,7 @@ Returns the current value of PrincipalType.
=head2 SetPrincipalType VALUE
-Set PrincipalType to VALUE.
+Set PrincipalType to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, PrincipalType will be stored as a varchar(25).)
@@ -154,7 +614,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 PrincipalId
-Returns the current value of PrincipalId.
+Returns the current value of PrincipalId.
(In the database, PrincipalId is stored as int(11).)
@@ -162,7 +622,7 @@ Returns the current value of PrincipalId.
=head2 SetPrincipalId VALUE
-Set PrincipalId to VALUE.
+Set PrincipalId to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, PrincipalId will be stored as a int(11).)
@@ -172,7 +632,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 RightName
-Returns the current value of RightName.
+Returns the current value of RightName.
(In the database, RightName is stored as varchar(25).)
@@ -180,7 +640,7 @@ Returns the current value of RightName.
=head2 SetRightName VALUE
-Set RightName to VALUE.
+Set RightName to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, RightName will be stored as a varchar(25).)
@@ -190,7 +650,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 ObjectType
-Returns the current value of ObjectType.
+Returns the current value of ObjectType.
(In the database, ObjectType is stored as varchar(25).)
@@ -198,7 +658,7 @@ Returns the current value of ObjectType.
=head2 SetObjectType VALUE
-Set ObjectType to VALUE.
+Set ObjectType to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, ObjectType will be stored as a varchar(25).)
@@ -208,7 +668,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 ObjectId
-Returns the current value of ObjectId.
+Returns the current value of ObjectId.
(In the database, ObjectId is stored as int(11).)
@@ -216,7 +676,7 @@ Returns the current value of ObjectId.
=head2 SetObjectId VALUE
-Set ObjectId to VALUE.
+Set ObjectId to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, ObjectId will be stored as a int(11).)
@@ -224,38 +684,34 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=cut
-=head2 DelegatedBy
+=head2 Creator
-Returns the current value of DelegatedBy.
-(In the database, DelegatedBy is stored as int(11).)
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+=cut
-=head2 SetDelegatedBy VALUE
-
-
-Set DelegatedBy to VALUE.
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, DelegatedBy will be stored as a int(11).)
+=head2 Created
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
=cut
-=head2 DelegatedFrom
-
-Returns the current value of DelegatedFrom.
-(In the database, DelegatedFrom is stored as int(11).)
-
+=head2 LastUpdatedBy
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
-=head2 SetDelegatedFrom VALUE
+=cut
-Set DelegatedFrom to VALUE.
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, DelegatedFrom will be stored as a int(11).)
+=head2 LastUpdated
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
=cut
@@ -263,46 +719,31 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
sub _CoreAccessible {
{
-
+
id =>
{read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
- PrincipalType =>
+ PrincipalType =>
{read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
- PrincipalId =>
+ PrincipalId =>
{read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
- RightName =>
+ RightName =>
{read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
- ObjectType =>
+ ObjectType =>
{read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
- ObjectId =>
- {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
- DelegatedBy =>
- {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
- DelegatedFrom =>
+ ObjectId =>
{read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
}
};
RT::Base->_ImportOverlays();
-=head1 SEE ALSO
-
-This class allows "overlay" methods to be placed
-into the following files _Overlay is for a System overlay by the original author,
-_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
-
-These overlay files can contain new subs or subs to replace existing subs in this module.
-
-Each of these files should begin with the line
-
- no warnings qw(redefine);
-
-so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
-
-RT::ACE_Overlay, RT::ACE_Vendor, RT::ACE_Local
-
-=cut
-
-
1;