import of rt 3.0.4
[freeside.git] / rt / lib / RT / ACE_Overlay.pm
diff --git a/rt/lib/RT/ACE_Overlay.pm b/rt/lib/RT/ACE_Overlay.pm
new file mode 100644 (file)
index 0000000..65e5a9c
--- /dev/null
@@ -0,0 +1,907 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+=head1 SYNOPSIS
+
+  use RT::ACE;
+  my $ace = new RT::ACE($CurrentUser);
+
+
+=head1 DESCRIPTION
+
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::ACE);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+use RT::Principals;
+use RT::Queues;
+use RT::Groups;
+
+use vars qw (
+  %LOWERCASERIGHTNAMES
+  %OBJECT_TYPES
+  %TICKET_METAPRINCIPALS
+);
+
+
+# {{{ Descriptions of rights
+
+=head1 Rights
+
+# Queue rights are the sort of queue rights that can only be granted
+# to real people or groups
+
+
+=begin testing
+
+my $Queue = RT::Queue->new($RT::SystemUser);
+
+is ($Queue->AvailableRights->{'DeleteTicket'} , 'Delete tickets', "Found the delete ticket right");
+is ($RT::System->AvailableRights->{'SuperUser'},  'Do anything and everything', "Found the superuser right");
+
+
+=end testing
+
+=cut
+
+
+
+
+# }}}
+
+# {{{ Descriptions of principals
+
+%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
+);
+
+# }}}
+
+
+# {{{ sub LoadByValues
+
+=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,
+                 @_ );
+
+    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_type, $object_id);
+    
+    if ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
+        $object_type = ref($args{'Object'});
+        $object_id = $args{'Object'}->id;
+    } elsif ($args{'ObjectId'} || $args{'ObjectType'}) {
+        $object_type = $args{'ObjectType'};
+        $object_id = $args{'ObjectId'};
+    } else {
+            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") );
+
+}
+
+# }}}
+
+# {{{ sub Create
+
+=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
+   DelegatedBy => The Principal->Id of the user delegating the right
+   DelegatedFrom => The id of the ACE which this new ACE is delegated from
+
+
+    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
+
+=cut
+
+sub Create {
+    my $self = shift;
+    my %args = ( PrincipalId   => undef,
+                 PrincipalType => undef,
+                 RightName     => undef,
+                 Object    => $RT::System,
+                 @_ );
+
+    # {{{ 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'} )
+        );
+    }
+
+    # }}}
+
+
+    if ($args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'})) {
+        use Carp;
+        $RT::Logger->crit(Carp::cluck("ACE::Create called with an ObjectType or an ObjectId"));
+    }
+
+
+    
+    unless ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
+            return ( 0, $self->loc("System error. Right not granted.") );
+    }
+    # {{{ 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
+    unless ( $args{'RightName'} ) {
+        return ( 0, $self->loc('Invalid right') );
+    }
+
+    $args{'RightName'} = $self->CanonicalizeRightName( $args{'RightName'} );
+
+    #check if it's a valid RightName
+    if ( ref ($args{'Object'} eq 'RT::Queue'  )) {
+        unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
+            $RT::Logger->warning("Couldn't validate right name". $args{'RightName'});
+            return ( 0, $self->loc('Invalid right') );
+        }
+    }
+    elsif ( ref ($args{'Object'} eq 'RT::Group'  )) {
+        unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
+            $RT::Logger->warning("Couldn't validate group right name". $args{'RightName'});
+            return ( 0, $self->loc('Invalid right') );
+        }
+    }
+    elsif ( ref ($args{'Object'} eq 'RT::System'  )) {
+        my $q = RT::Queue->new($self->CurrentUser);
+        my $g = RT::Group->new($self->CurrentUser);
+
+        unless (( exists $g->AvailableRights->{ $args{'RightName'} } )
+        || ( exists $g->AvailableRights->{ $args{'RightName'} } )
+        || ( exists $RT::System->AvailableRights->{ $args{'RightName'} } ) ) {
+            $RT::Logger->warning("Couldn't validate system right name - ". $args{'RightName'});
+            return ( 0, $self->loc('Invalid right') );
+        }
+    }
+
+    unless ( $args{'RightName'} ) {
+        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    => ref($args{'Object'}),
+                       ObjectId      => $args{'Object'}->id,
+                       DelegatedBy   => 0,
+                       DelegatedFrom => 0 );
+    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,
+                                   DelegatedBy   => 0,
+                                   DelegatedFrom => 0 );
+
+    #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
+    RT::Principal->_InvalidateACLCache();
+
+    if ( $id > 0 ) {
+        return ( $id, $self->loc('Right Granted') );
+    }
+    else {
+        return ( 0, $self->loc('System error. Right not granted.') );
+    }
+}
+
+# }}}
+
+# {{{ sub Delegate
+
+=head2 Delegate <PARAMS>
+
+This routine delegates the current ACE to a principal specified by the
+B<PrincipalId>  parameter.
+
+Returns an error if the current user doesn't have the right to be delegated
+or doesn't have the right to delegate rights.
+
+Always returns a tuple of (ReturnValue, Message)
+
+=begin testing
+
+use_ok(RT::User);
+my $user_a = RT::User->new($RT::SystemUser);
+$user_a->Create( Name => 'DelegationA', Privileged => 1);
+ok ($user_a->Id, "Created delegation user a");
+
+my $user_b = RT::User->new($RT::SystemUser);
+$user_b->Create( Name => 'DelegationB', Privileged => 1);
+ok ($user_b->Id, "Created delegation user b");
+
+
+use_ok(RT::Queue);
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Create(Name =>'DelegationTest');
+ok ($q->Id, "Created a delegation test queue");
+
+
+#------ First, we test whether a user can delegate a right that's been granted to him personally 
+my ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object => $RT::System, Right => 'AdminOwnPersonalGroups');
+ok($val, $msg);
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => 'OwnTicket');
+ok($val, $msg);
+
+ok($user_a->HasRight( Object => $RT::System, Right => 'AdminOwnPersonalGroups')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
+
+my $a_delegates = RT::Group->new($user_a);
+$a_delegates->CreatePersonalGroup(Name => 'Delegates');
+ok( $a_delegates->Id   ,"user a creates a personal group 'Delegates'");
+ok( $a_delegates->AddMember($user_b->PrincipalId)   ,"user a adds user b to personal group 'delegates'");
+
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b does not have the right to OwnTicket' in queue 'DelegationTest'");
+ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to 'OwnTicket' in queue 'DelegationTest'");
+ok(!$user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a does not have the right 'delegate rights'");
+
+
+my $own_ticket_ace = RT::ACE->new($user_a);
+my $user_a_equiv_group = RT::Group->new($user_a);
+$user_a_equiv_group->LoadACLEquivalenceGroup($user_a->PrincipalObj);
+ok ($user_a_equiv_group->Id, "Loaded the user A acl equivalence group");
+my $user_b_equiv_group = RT::Group->new($user_b);
+$user_b_equiv_group->LoadACLEquivalenceGroup($user_b->PrincipalObj);
+ok ($user_b_equiv_group->Id, "Loaded the user B acl equivalence group");
+$own_ticket_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $user_a_equiv_group->PrincipalId, Object=>$q, RightName => 'OwnTicket');
+
+ok ($own_ticket_ace->Id, "Found the ACE we want to test with for now");
+
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
+ok( !$val ,"user a tries and fails to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight( Right => 'DelegateRights');
+ok($val, "user a is granted the right to 'delegate rights' - $msg");
+
+ok($user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
+
+ok( $val    ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
+my $delegated_ace = RT::ACE->new($user_a);
+$delegated_ace->LoadByValues ( Object => $q, RightName => 'OwnTicket', PrincipalType => 'Group',
+PrincipalId => $a_delegates->PrincipalId, DelegatedBy => $user_a->PrincipalId, DelegatedFrom => $own_ticket_ace->Id);
+ok ($delegated_ace->Id, "Found the delegated ACE");
+
+ok(    $a_delegates->DeleteMember($user_b->PrincipalId)  ,"user a removes b from pg 'delegates'");
+ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+ok(  $a_delegates->AddMember($user_b->PrincipalId)    ,"user a adds user b to personal group 'delegates'");
+ok(   $user_b->HasRight(Right => 'OwnTicket', Object=> $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+ok(   $delegated_ace->Delete ,"user a revokes pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest'");
+ok( ! $user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
+ok(  $val  ,"user a delegates pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest' - $msg");
+
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $user_a->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest". $msg);
+
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+ ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, $msg);
+
+ ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user a has the right to own tickets in queue 'DelegationTest'");
+
+ ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+# {{{ get back to a known clean state 
+($val, $msg) = $user_a->PrincipalObj->RevokeRight( Object => $q, Right => 'OwnTicket');
+ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest -". $msg);
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"make sure that user a can't own tickets in queue 'DelegationTest'");
+# }}}
+
+
+# {{{ Set up some groups and membership
+my $del1 = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del1->CreateUserDefinedGroup(Name => 'Del1');
+ok( $val   ,"create a group del1 - $msg");
+
+my $del2 = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2->CreateUserDefinedGroup(Name => 'Del2');
+ok( $val   ,"create a group del2 - $msg");
+($val, $msg) = $del1->AddMember($del2->PrincipalId);
+ok( $val,"make del2 a member of del1 - $msg");
+
+my $del2a = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2a->CreateUserDefinedGroup(Name => 'Del2a');
+ok( $val   ,"create a group del2a - $msg");
+($val, $msg) = $del2->AddMember($del2a->PrincipalId);  
+ok($val    ,"make del2a a member of del2 - $msg");
+
+my $del2b = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2b->CreateUserDefinedGroup(Name => 'Del2b');
+ok( $val   ,"create a group del2b - $msg");
+($val, $msg) = $del2->AddMember($del2b->PrincipalId);  
+ok($val    ,"make del2b a member of del2 - $msg");
+
+($val, $msg) = $del2->AddMember($user_a->PrincipalId) ;
+ok($val,"make 'user a' a member of del2 - $msg");
+
+($val, $msg) = $del2b->AddMember($user_a->PrincipalId) ;
+ok($val,"make 'user a' a member of del2b - $msg");
+
+# }}}
+
+# {{{ Grant a right to a group and make sure that a submember can delegate the right and that it does not get yanked
+# when a user is removed as a submember, when they're a sumember through another path 
+($val, $msg) = $del1->PrincipalObj->GrantRight( Object=> $q, Right => 'OwnTicket');
+ok( $val   ,"grant del1  the right to 'OwnTicket' in queue 'DelegationTest' - $msg");
+
+ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"make sure that user a can own tickets in queue 'DelegationTest'");
+
+my $group_ace= RT::ACE->new($user_a);
+$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object => $q, RightName => 'OwnTicket');
+
+ok ($group_ace->Id, "Found the ACE we want to test with for now");
+
+($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
+
+ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+
+($val, $msg) = $del2b->DeleteMember($user_a->PrincipalId);
+ok( $val   ,"remove user a from group del2b - $msg");
+ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to own tickets in queue 'DelegationTest'");
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+# }}}
+
+# {{{ When a  user is removed froom a group by the only path they're in there by, make sure the delegations go away
+($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
+ok( $val   ,"remove user a from group del2 - $msg");
+ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest' ");
+ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest' ");
+# }}}
+
+($val, $msg) = $del2->AddMember($user_a->PrincipalId);
+ok( $val   ,"make user a a member of group del2 - $msg");
+
+($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+
+my $del2_right = RT::ACE->new($user_a);
+$del2_right->LoadByValues( PrincipalId => $del2->PrincipalId, PrincipalType => 'Group', Object => $q, RightName => 'OwnTicket');
+ok ($del2_right->Id, "Found the right");
+
+($val, $msg) = $del2_right->Delegate(PrincipalId => $a_delegates->PrincipalId);
+ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' gotten via del2 to personal group 'delegates' - $msg");
+
+# They have it via del1 and del2
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+
+($val, $msg) = $del2->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "revoke the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does has the right to own tickets in queue 'DelegationTest' via del1");
+ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+
+
+$group_ace= RT::ACE->new($user_a);
+$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object=>$q, RightName => 'OwnTicket');
+
+ok ($group_ace->Id, "Found the ACE we want to test with for now");
+
+($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
+
+ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
+ok( $val   ,"remove user a from group del2 - $msg");
+
+ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+
+
+=end testing
+
+=cut
+
+sub Delegate {
+    my $self = shift;
+    my %args = ( PrincipalId => undef,
+                 @_ );
+
+    unless ( $self->Id ) {
+        return ( 0, $self->loc("Right not loaded.") );
+    }
+    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
+
+    # First, we check to se if the user is delegating rights and
+    # they have the permission to
+    unless ( $self->CurrentUser->HasRight(Right => 'DelegateRights', Object => $self->Object) ) {
+        return ( 0, $self->loc("Permission Denied") );
+    }
+
+    unless ( $self->PrincipalObj->IsGroup ) {
+        return ( 0, $self->loc("System Error") );
+    }
+    unless ( $self->PrincipalObj->Object->HasMemberRecursively(
+                                                $self->CurrentUser->PrincipalObj
+             )
+      ) {
+        return ( 0, $self->loc("Permission Denied") );
+    }
+
+    # }}}
+
+    my $concurrency_check = RT::ACE->new($RT::SystemUser);
+    $concurrency_check->Load( $self->Id );
+    unless ( $concurrency_check->Id ) {
+        $RT::Logger->crit(
+                   "Trying to delegate a right which had already been deleted");
+        return ( 0, $self->loc('Permission Denied') );
+    }
+
+    my $delegated_ace = RT::ACE->new( $self->CurrentUser );
+
+    # Make sure the right doesn't already exist.
+    $delegated_ace->LoadByCols( PrincipalId   => $princ_obj->Id,
+                                PrincipalType => 'Group',
+                                RightName     => $self->__Value('RightName'),
+                                ObjectType    => $self->__Value('ObjectType'),
+                                ObjectId      => $self->__Value('ObjectId'),
+                                DelegatedBy => $self->CurrentUser->PrincipalId,
+                                DelegatedFrom => $self->id );
+    if ( $delegated_ace->Id ) {
+        return ( 0, $self->loc('That principal already has that right') );
+    }
+    my $id = $delegated_ace->SUPER::Create(
+        PrincipalId   => $princ_obj->Id,
+        PrincipalType => 'Group',          # do we want to hardcode this?
+        RightName     => $self->__Value('RightName'),
+        ObjectType    => $self->__Value('ObjectType'),
+        ObjectId      => $self->__Value('ObjectId'),
+        DelegatedBy   => $self->CurrentUser->PrincipalId,
+        DelegatedFrom => $self->id );
+
+    #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();
+
+    if ( $id > 0 ) {
+        return ( $id, $self->loc('Right Delegated') );
+    }
+    else {
+        return ( 0, $self->loc('System error. Right not delegated.') );
+    }
+}
+
+# }}}
+
+# {{{ sub Delete 
+
+=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)
+           && $self->__Value('DelegatedBy') == 0 )
+         || ( $self->__Value('DelegatedBy') == $self->CurrentUser->PrincipalId )
+      ) {
+        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 $delegated_from_this = RT::ACL->new($RT::SystemUser);
+    $delegated_from_this->Limit( FIELD    => 'DelegatedFrom',
+                                 OPERATOR => '=',
+                                 VALUE    => $self->Id );
+
+    my $delete_succeeded = 1;
+    my $submsg;
+    while ( my $delegated_ace = $delegated_from_this->Next ) {
+        ( $delete_succeeded, $submsg ) =
+          $delegated_ace->_Delete( InsideTransaction => 1 );
+        last if ($delete_succeeded);
+    }
+
+    unless ($delete_succeeded) {
+        $RT::Handle->Rollback() unless $InsideTransaction;
+        return ( 0, $self->loc('Right could not be revoked') );
+    }
+
+    my ( $val, $msg ) = $self->SUPER::Delete(@_);
+
+    #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();
+
+    if ($val) {
+        $RT::Handle->Commit() unless $InsideTransaction;
+        return ( $val, $self->loc('Right revoked') );
+    }
+    else {
+        $RT::Handle->Rollback() unless $InsideTransaction;
+        return ( 0, $self->loc('Right could not be revoked') );
+    }
+}
+
+# }}}
+
+# {{{ sub _BootstrapCreate 
+
+=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 CanonicalizeRightName
+
+=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;
+    my $right = shift;
+    $right = lc $right;
+    if ( exists $LOWERCASERIGHTNAMES{"$right"} ) {
+        return ( $LOWERCASERIGHTNAMES{"$right"} );
+    }
+    else {
+        return (undef);
+    }
+}
+
+# }}}
+
+
+# {{{ sub Object
+
+=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->ObjectType );
+        return (undef);
+    }
+}
+
+# }}}
+
+# {{{ sub PrincipalObj
+
+=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);
+
+}
+
+# }}}
+
+# {{{ ACL related methods
+
+# {{{ sub _Set
+
+sub _Set {
+    my $self = shift;
+    return ( 0, $self->loc("ACEs can only be created and deleted.") );
+}
+
+# }}}
+
+# {{{ sub _Value
+
+sub _Value {
+    my $self = shift;
+
+    if ( $self->__Value('DelegatedBy') eq $self->CurrentUser->PrincipalId ) {
+        return ( $self->__Value(@_) );
+    }
+    elsif ( $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;
+    }
+}
+
+# }}}
+
+
+# }}}
+
+# {{{ _CanonicalizePrincipal 
+
+=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::cluck);
+        $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 " . $self->__Value('ObjectId') );
+            return ( 0, $self->loc('System error. Right not granted.') );
+        }
+        $princ_obj  = $equiv_group->PrincipalObj();
+        $princ_type = 'Group';
+
+    }
+    return ( $princ_obj, $princ_type );
+}
+
+# }}}
+1;