import of rt 3.0.9
[freeside.git] / rt / lib / RT / ACE_Overlay.pm
1 # BEGIN LICENSE BLOCK
2
3 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
4
5 # (Except where explictly superceded by other copyright notices)
6
7 # This work is made available to you under the terms of Version 2 of
8 # the GNU General Public License. A copy of that license should have
9 # been provided with this software, but in any event can be snarfed
10 # from www.gnu.org.
11
12 # This work is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 # General Public License for more details.
16
17 # Unless otherwise specified, all modifications, corrections or
18 # extensions to this work which alter its source code become the
19 # property of Best Practical Solutions, LLC when submitted for
20 # inclusion in the work.
21
22
23 # END LICENSE BLOCK
24 =head1 SYNOPSIS
25
26   use RT::ACE;
27   my $ace = new RT::ACE($CurrentUser);
28
29
30 =head1 DESCRIPTION
31
32
33
34 =head1 METHODS
35
36 =begin testing
37
38 ok(require RT::ACE);
39
40 =end testing
41
42 =cut
43
44 use strict;
45 no warnings qw(redefine);
46 use RT::Principals;
47 use RT::Queues;
48 use RT::Groups;
49
50 use vars qw (
51   %LOWERCASERIGHTNAMES
52   %OBJECT_TYPES
53   %TICKET_METAPRINCIPALS
54 );
55
56
57 # {{{ Descriptions of rights
58
59 =head1 Rights
60
61 # Queue rights are the sort of queue rights that can only be granted
62 # to real people or groups
63
64
65 =begin testing
66
67 my $Queue = RT::Queue->new($RT::SystemUser);
68
69 is ($Queue->AvailableRights->{'DeleteTicket'} , 'Delete tickets', "Found the delete ticket right");
70 is ($RT::System->AvailableRights->{'SuperUser'},  'Do anything and everything', "Found the superuser right");
71
72
73 =end testing
74
75 =cut
76
77
78
79
80 # }}}
81
82 # {{{ Descriptions of principals
83
84 %TICKET_METAPRINCIPALS = (
85     Owner     => 'The owner of a ticket',                             # loc_pair
86     Requestor => 'The requestor of a ticket',                         # loc_pair
87     Cc        => 'The CC of a ticket',                                # loc_pair
88     AdminCc   => 'The administrative CC of a ticket',                 # loc_pair
89 );
90
91 # }}}
92
93
94 # {{{ sub LoadByValues
95
96 =head2 LoadByValues PARAMHASH
97
98 Load an ACE by specifying a paramhash with the following fields:
99
100               PrincipalId => undef,
101               PrincipalType => undef,
102               RightName => undef,
103
104         And either:
105
106               Object => undef,
107
108             OR
109
110         ObjectType => undef,
111         ObjectId => undef
112
113 =cut
114
115 sub LoadByValues {
116     my $self = shift;
117     my %args = ( PrincipalId   => undef,
118                  PrincipalType => undef,
119                  RightName     => undef,
120                  Object    => undef,
121                  ObjectId    => undef,
122                  ObjectType    => undef,
123                  @_ );
124
125     my $princ_obj;
126     ( $princ_obj, $args{'PrincipalType'} ) =
127       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
128                                      $args{'PrincipalType'} );
129
130     unless ( $princ_obj->id ) {
131         return ( 0,
132                  $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
133         );
134     }
135
136     my ($object_type, $object_id);
137     
138     if ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
139         $object_type = ref($args{'Object'});
140         $object_id = $args{'Object'}->id;
141     } elsif ($args{'ObjectId'} || $args{'ObjectType'}) {
142         $object_type = $args{'ObjectType'};
143         $object_id = $args{'ObjectId'};
144     } else {
145             return ( 0, $self->loc("System error. Right not granted.") );
146     }
147
148     $self->LoadByCols( PrincipalId   => $princ_obj->Id,
149                        PrincipalType => $args{'PrincipalType'},
150                        RightName     => $args{'RightName'},
151                        ObjectType    => $object_type,
152                        ObjectId      => $object_id);
153
154     #If we couldn't load it.
155     unless ( $self->Id ) {
156         return ( 0, $self->loc("ACE not found") );
157     }
158
159     # if we could
160     return ( $self->Id, $self->loc("Right Loaded") );
161
162 }
163
164 # }}}
165
166 # {{{ sub Create
167
168 =head2 Create <PARAMS>
169
170 PARAMS is a parameter hash with the following elements:
171
172    PrincipalId => The id of an RT::Principal object
173    PrincipalType => "User" "Group" or any Role type
174    RightName => the name of a right. in any case
175    DelegatedBy => The Principal->Id of the user delegating the right
176    DelegatedFrom => The id of the ACE which this new ACE is delegated from
177
178
179     Either:
180
181    Object => An object to create rights for. ususally, an RT::Queue or RT::Group
182              This should always be a DBIx::SearchBuilder::Record subclass
183
184         OR
185
186    ObjectType => the type of the object in question (ref ($object))
187    ObjectId => the id of the object in question $object->Id
188
189 =cut
190
191 sub Create {
192     my $self = shift;
193     my %args = ( PrincipalId   => undef,
194                  PrincipalType => undef,
195                  RightName     => undef,
196                  Object    => $RT::System,
197                  @_ );
198
199     # {{{ Validate the principal
200     my $princ_obj;
201     ( $princ_obj, $args{'PrincipalType'} ) =
202       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
203                                      $args{'PrincipalType'} );
204
205     unless ( $princ_obj->id ) {
206         return ( 0,
207                  $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
208         );
209     }
210
211     # }}}
212
213
214     if ($args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'})) {
215         use Carp;
216         $RT::Logger->crit(Carp::cluck("ACE::Create called with an ObjectType or an ObjectId"));
217     }
218
219
220     
221     unless ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
222             return ( 0, $self->loc("System error. Right not granted.") );
223     }
224     # {{{ Check the ACL
225
226     if (ref( $args{'Object'}) eq 'RT::Group' ) {
227         unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
228                                                   Right => 'AdminGroup' )
229           ) {
230             return ( 0, $self->loc('Permission Denied') );
231         }
232     }
233
234     else {
235         unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
236             return ( 0, $self->loc('Permission Denied') );
237         }
238     }
239     # }}}
240
241     # {{{ Canonicalize and check the right name
242     unless ( $args{'RightName'} ) {
243         return ( 0, $self->loc('Invalid right') );
244     }
245
246     $args{'RightName'} = $self->CanonicalizeRightName( $args{'RightName'} );
247
248     #check if it's a valid RightName
249     if ( ref ($args{'Object'} eq 'RT::Queue'  )) {
250         unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
251             $RT::Logger->warning("Couldn't validate right name". $args{'RightName'});
252             return ( 0, $self->loc('Invalid right') );
253         }
254     }
255     elsif ( ref ($args{'Object'} eq 'RT::Group'  )) {
256         unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
257             $RT::Logger->warning("Couldn't validate group right name". $args{'RightName'});
258             return ( 0, $self->loc('Invalid right') );
259         }
260     }
261     elsif ( ref ($args{'Object'} eq 'RT::System'  )) {
262         my $q = RT::Queue->new($self->CurrentUser);
263         my $g = RT::Group->new($self->CurrentUser);
264
265         unless (( exists $g->AvailableRights->{ $args{'RightName'} } )
266         || ( exists $g->AvailableRights->{ $args{'RightName'} } )
267         || ( exists $RT::System->AvailableRights->{ $args{'RightName'} } ) ) {
268             $RT::Logger->warning("Couldn't validate system right name - ". $args{'RightName'});
269             return ( 0, $self->loc('Invalid right') );
270         }
271     }
272
273     unless ( $args{'RightName'} ) {
274         return ( 0, $self->loc('Invalid right') );
275     }
276     # }}}
277
278     # Make sure the right doesn't already exist.
279     $self->LoadByCols( PrincipalId   => $princ_obj->id,
280                        PrincipalType => $args{'PrincipalType'},
281                        RightName     => $args{'RightName'},
282                        ObjectType    => ref($args{'Object'}),
283                        ObjectId      => $args{'Object'}->id,
284                        DelegatedBy   => 0,
285                        DelegatedFrom => 0 );
286     if ( $self->Id ) {
287         return ( 0, $self->loc('That principal already has that right') );
288     }
289
290     my $id = $self->SUPER::Create( PrincipalId   => $princ_obj->id,
291                                    PrincipalType => $args{'PrincipalType'},
292                                    RightName     => $args{'RightName'},
293                                    ObjectType    => ref( $args{'Object'} ),
294                                    ObjectId      => $args{'Object'}->id,
295                                    DelegatedBy   => 0,
296                                    DelegatedFrom => 0 );
297
298     #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
299     RT::Principal->_InvalidateACLCache();
300
301     if ( $id > 0 ) {
302         return ( $id, $self->loc('Right Granted') );
303     }
304     else {
305         return ( 0, $self->loc('System error. Right not granted.') );
306     }
307 }
308
309 # }}}
310
311 # {{{ sub Delegate
312
313 =head2 Delegate <PARAMS>
314
315 This routine delegates the current ACE to a principal specified by the
316 B<PrincipalId>  parameter.
317
318 Returns an error if the current user doesn't have the right to be delegated
319 or doesn't have the right to delegate rights.
320
321 Always returns a tuple of (ReturnValue, Message)
322
323 =begin testing
324
325 use_ok(RT::User);
326 my $user_a = RT::User->new($RT::SystemUser);
327 $user_a->Create( Name => 'DelegationA', Privileged => 1);
328 ok ($user_a->Id, "Created delegation user a");
329
330 my $user_b = RT::User->new($RT::SystemUser);
331 $user_b->Create( Name => 'DelegationB', Privileged => 1);
332 ok ($user_b->Id, "Created delegation user b");
333
334
335 use_ok(RT::Queue);
336 my $q = RT::Queue->new($RT::SystemUser);
337 $q->Create(Name =>'DelegationTest');
338 ok ($q->Id, "Created a delegation test queue");
339
340
341 #------ First, we test whether a user can delegate a right that's been granted to him personally 
342 my ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object => $RT::System, Right => 'AdminOwnPersonalGroups');
343 ok($val, $msg);
344
345 ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => 'OwnTicket');
346 ok($val, $msg);
347
348 ok($user_a->HasRight( Object => $RT::System, Right => 'AdminOwnPersonalGroups')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
349
350 my $a_delegates = RT::Group->new($user_a);
351 $a_delegates->CreatePersonalGroup(Name => 'Delegates');
352 ok( $a_delegates->Id   ,"user a creates a personal group 'Delegates'");
353 ok( $a_delegates->AddMember($user_b->PrincipalId)   ,"user a adds user b to personal group 'delegates'");
354
355 ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b does not have the right to OwnTicket' in queue 'DelegationTest'");
356 ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to 'OwnTicket' in queue 'DelegationTest'");
357 ok(!$user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a does not have the right 'delegate rights'");
358
359
360 my $own_ticket_ace = RT::ACE->new($user_a);
361 my $user_a_equiv_group = RT::Group->new($user_a);
362 $user_a_equiv_group->LoadACLEquivalenceGroup($user_a->PrincipalObj);
363 ok ($user_a_equiv_group->Id, "Loaded the user A acl equivalence group");
364 my $user_b_equiv_group = RT::Group->new($user_b);
365 $user_b_equiv_group->LoadACLEquivalenceGroup($user_b->PrincipalObj);
366 ok ($user_b_equiv_group->Id, "Loaded the user B acl equivalence group");
367 $own_ticket_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $user_a_equiv_group->PrincipalId, Object=>$q, RightName => 'OwnTicket');
368
369 ok ($own_ticket_ace->Id, "Found the ACE we want to test with for now");
370
371
372 ($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
373 ok( !$val ,"user a tries and fails to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
374
375
376 ($val, $msg) = $user_a->PrincipalObj->GrantRight( Right => 'DelegateRights');
377 ok($val, "user a is granted the right to 'delegate rights' - $msg");
378
379 ok($user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
380
381 ($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
382
383 ok( $val    ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
384 ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
385 my $delegated_ace = RT::ACE->new($user_a);
386 $delegated_ace->LoadByValues ( Object => $q, RightName => 'OwnTicket', PrincipalType => 'Group',
387 PrincipalId => $a_delegates->PrincipalId, DelegatedBy => $user_a->PrincipalId, DelegatedFrom => $own_ticket_ace->Id);
388 ok ($delegated_ace->Id, "Found the delegated ACE");
389
390 ok(    $a_delegates->DeleteMember($user_b->PrincipalId)  ,"user a removes b from pg 'delegates'");
391 ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
392 ok(  $a_delegates->AddMember($user_b->PrincipalId)    ,"user a adds user b to personal group 'delegates'");
393 ok(   $user_b->HasRight(Right => 'OwnTicket', Object=> $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
394 ok(   $delegated_ace->Delete ,"user a revokes pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest'");
395 ok( ! $user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
396
397 ($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
398 ok(  $val  ,"user a delegates pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest' - $msg");
399
400 ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
401
402 ($val, $msg) = $user_a->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
403 ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest". $msg);
404
405 ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
406
407  ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
408
409 ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
410 ok($val, $msg);
411
412  ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user a has the right to own tickets in queue 'DelegationTest'");
413
414  ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
415
416 # {{{ get back to a known clean state 
417 ($val, $msg) = $user_a->PrincipalObj->RevokeRight( Object => $q, Right => 'OwnTicket');
418 ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest -". $msg);
419 ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"make sure that user a can't own tickets in queue 'DelegationTest'");
420 # }}}
421
422
423 # {{{ Set up some groups and membership
424 my $del1 = RT::Group->new($RT::SystemUser);
425 ($val, $msg) = $del1->CreateUserDefinedGroup(Name => 'Del1');
426 ok( $val   ,"create a group del1 - $msg");
427
428 my $del2 = RT::Group->new($RT::SystemUser);
429 ($val, $msg) = $del2->CreateUserDefinedGroup(Name => 'Del2');
430 ok( $val   ,"create a group del2 - $msg");
431 ($val, $msg) = $del1->AddMember($del2->PrincipalId);
432 ok( $val,"make del2 a member of del1 - $msg");
433
434 my $del2a = RT::Group->new($RT::SystemUser);
435 ($val, $msg) = $del2a->CreateUserDefinedGroup(Name => 'Del2a');
436 ok( $val   ,"create a group del2a - $msg");
437 ($val, $msg) = $del2->AddMember($del2a->PrincipalId);  
438 ok($val    ,"make del2a a member of del2 - $msg");
439
440 my $del2b = RT::Group->new($RT::SystemUser);
441 ($val, $msg) = $del2b->CreateUserDefinedGroup(Name => 'Del2b');
442 ok( $val   ,"create a group del2b - $msg");
443 ($val, $msg) = $del2->AddMember($del2b->PrincipalId);  
444 ok($val    ,"make del2b a member of del2 - $msg");
445
446 ($val, $msg) = $del2->AddMember($user_a->PrincipalId) ;
447 ok($val,"make 'user a' a member of del2 - $msg");
448
449 ($val, $msg) = $del2b->AddMember($user_a->PrincipalId) ;
450 ok($val,"make 'user a' a member of del2b - $msg");
451
452 # }}}
453
454 # {{{ Grant a right to a group and make sure that a submember can delegate the right and that it does not get yanked
455 # when a user is removed as a submember, when they're a sumember through another path 
456 ($val, $msg) = $del1->PrincipalObj->GrantRight( Object=> $q, Right => 'OwnTicket');
457 ok( $val   ,"grant del1  the right to 'OwnTicket' in queue 'DelegationTest' - $msg");
458
459 ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"make sure that user a can own tickets in queue 'DelegationTest'");
460
461 my $group_ace= RT::ACE->new($user_a);
462 $group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object => $q, RightName => 'OwnTicket');
463
464 ok ($group_ace->Id, "Found the ACE we want to test with for now");
465
466 ($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
467
468 ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
469 ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
470
471
472 ($val, $msg) = $del2b->DeleteMember($user_a->PrincipalId);
473 ok( $val   ,"remove user a from group del2b - $msg");
474 ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to own tickets in queue 'DelegationTest'");
475 ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
476
477 # }}}
478
479 # {{{ When a  user is removed froom a group by the only path they're in there by, make sure the delegations go away
480 ($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
481 ok( $val   ,"remove user a from group del2 - $msg");
482 ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest' ");
483 ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest' ");
484 # }}}
485
486 ($val, $msg) = $del2->AddMember($user_a->PrincipalId);
487 ok( $val   ,"make user a a member of group del2 - $msg");
488
489 ($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
490 ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
491
492 my $del2_right = RT::ACE->new($user_a);
493 $del2_right->LoadByValues( PrincipalId => $del2->PrincipalId, PrincipalType => 'Group', Object => $q, RightName => 'OwnTicket');
494 ok ($del2_right->Id, "Found the right");
495
496 ($val, $msg) = $del2_right->Delegate(PrincipalId => $a_delegates->PrincipalId);
497 ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' gotten via del2 to personal group 'delegates' - $msg");
498
499 # They have it via del1 and del2
500 ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b has the right to own tickets in queue 'DelegationTest'");
501
502
503 ($val, $msg) = $del2->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
504 ok($val, "revoke the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
505 ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does has the right to own tickets in queue 'DelegationTest' via del1");
506 ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
507
508 ($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
509 ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
510
511
512 $group_ace= RT::ACE->new($user_a);
513 $group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object=>$q, RightName => 'OwnTicket');
514
515 ok ($group_ace->Id, "Found the ACE we want to test with for now");
516
517 ($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
518
519 ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
520
521 ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
522
523 ($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
524 ok( $val   ,"remove user a from group del2 - $msg");
525
526 ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest'");
527
528 ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
529
530
531
532 =end testing
533
534 =cut
535
536 sub Delegate {
537     my $self = shift;
538     my %args = ( PrincipalId => undef,
539                  @_ );
540
541     unless ( $self->Id ) {
542         return ( 0, $self->loc("Right not loaded.") );
543     }
544     my $princ_obj;
545     ( $princ_obj, $args{'PrincipalType'} ) =
546       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
547                                      $args{'PrincipalType'} );
548
549     unless ( $princ_obj->id ) {
550         return ( 0,
551                  $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
552         );
553     }
554
555     # }}}
556
557     # {{{ Check the ACL
558
559     # First, we check to se if the user is delegating rights and
560     # they have the permission to
561     unless ( $self->CurrentUser->HasRight(Right => 'DelegateRights', Object => $self->Object) ) {
562         return ( 0, $self->loc("Permission Denied") );
563     }
564
565     unless ( $self->PrincipalObj->IsGroup ) {
566         return ( 0, $self->loc("System Error") );
567     }
568     unless ( $self->PrincipalObj->Object->HasMemberRecursively(
569                                                 $self->CurrentUser->PrincipalObj
570              )
571       ) {
572         return ( 0, $self->loc("Permission Denied") );
573     }
574
575     # }}}
576
577     my $concurrency_check = RT::ACE->new($RT::SystemUser);
578     $concurrency_check->Load( $self->Id );
579     unless ( $concurrency_check->Id ) {
580         $RT::Logger->crit(
581                    "Trying to delegate a right which had already been deleted");
582         return ( 0, $self->loc('Permission Denied') );
583     }
584
585     my $delegated_ace = RT::ACE->new( $self->CurrentUser );
586
587     # Make sure the right doesn't already exist.
588     $delegated_ace->LoadByCols( PrincipalId   => $princ_obj->Id,
589                                 PrincipalType => 'Group',
590                                 RightName     => $self->__Value('RightName'),
591                                 ObjectType    => $self->__Value('ObjectType'),
592                                 ObjectId      => $self->__Value('ObjectId'),
593                                 DelegatedBy => $self->CurrentUser->PrincipalId,
594                                 DelegatedFrom => $self->id );
595     if ( $delegated_ace->Id ) {
596         return ( 0, $self->loc('That principal already has that right') );
597     }
598     my $id = $delegated_ace->SUPER::Create(
599         PrincipalId   => $princ_obj->Id,
600         PrincipalType => 'Group',          # do we want to hardcode this?
601         RightName     => $self->__Value('RightName'),
602         ObjectType    => $self->__Value('ObjectType'),
603         ObjectId      => $self->__Value('ObjectId'),
604         DelegatedBy   => $self->CurrentUser->PrincipalId,
605         DelegatedFrom => $self->id );
606
607     #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
608     # TODO what about the groups key cache?
609     RT::Principal->_InvalidateACLCache();
610
611     if ( $id > 0 ) {
612         return ( $id, $self->loc('Right Delegated') );
613     }
614     else {
615         return ( 0, $self->loc('System error. Right not delegated.') );
616     }
617 }
618
619 # }}}
620
621 # {{{ sub Delete 
622
623 =head2 Delete { InsideTransaction => undef}
624
625 Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
626 If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
627 Really, DBIx::SearchBuilder should use and/or fake subtransactions
628
629 This routine will also recurse and delete any delegations of this right
630
631 =cut
632
633 sub Delete {
634     my $self = shift;
635
636     unless ( $self->Id ) {
637         return ( 0, $self->loc('Right not loaded.') );
638     }
639
640     # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
641     # or if it's a delegated ACE and it was delegated by the current user
642     unless (
643          (    $self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)
644            && $self->__Value('DelegatedBy') == 0 )
645          || ( $self->__Value('DelegatedBy') == $self->CurrentUser->PrincipalId )
646       ) {
647         return ( 0, $self->loc('Permission Denied') );
648     }
649     $self->_Delete(@_);
650 }
651
652 # Helper for Delete with no ACL check
653 sub _Delete {
654     my $self = shift;
655     my %args = ( InsideTransaction => undef,
656                  @_ );
657
658     my $InsideTransaction = $args{'InsideTransaction'};
659
660     $RT::Handle->BeginTransaction() unless $InsideTransaction;
661
662     my $delegated_from_this = RT::ACL->new($RT::SystemUser);
663     $delegated_from_this->Limit( FIELD    => 'DelegatedFrom',
664                                  OPERATOR => '=',
665                                  VALUE    => $self->Id );
666
667     my $delete_succeeded = 1;
668     my $submsg;
669     while ( my $delegated_ace = $delegated_from_this->Next ) {
670         ( $delete_succeeded, $submsg ) =
671           $delegated_ace->_Delete( InsideTransaction => 1 );
672         last if ($delete_succeeded);
673     }
674
675     unless ($delete_succeeded) {
676         $RT::Handle->Rollback() unless $InsideTransaction;
677         return ( 0, $self->loc('Right could not be revoked') );
678     }
679
680     my ( $val, $msg ) = $self->SUPER::Delete(@_);
681
682     #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
683     # TODO what about the groups key cache?
684     RT::Principal->_InvalidateACLCache();
685
686     if ($val) {
687         $RT::Handle->Commit() unless $InsideTransaction;
688         return ( $val, $self->loc('Right revoked') );
689     }
690     else {
691         $RT::Handle->Rollback() unless $InsideTransaction;
692         return ( 0, $self->loc('Right could not be revoked') );
693     }
694 }
695
696 # }}}
697
698 # {{{ sub _BootstrapCreate 
699
700 =head2 _BootstrapCreate
701
702 Grant a right with no error checking and no ACL. this is _only_ for 
703 installation. If you use this routine without the author's explicit 
704 written approval, he will hunt you down and make you spend eternity
705 translating mozilla's code into FORTRAN or intercal.
706
707 If you think you need this routine, you've mistaken. 
708
709 =cut
710
711 sub _BootstrapCreate {
712     my $self = shift;
713     my %args = (@_);
714
715     # When bootstrapping, make sure we get the _right_ users
716     if ( $args{'UserId'} ) {
717         my $user = RT::User->new( $self->CurrentUser );
718         $user->Load( $args{'UserId'} );
719         delete $args{'UserId'};
720         $args{'PrincipalId'}   = $user->PrincipalId;
721         $args{'PrincipalType'} = 'User';
722     }
723
724     my $id = $self->SUPER::Create(%args);
725
726     if ( $id > 0 ) {
727         return ($id);
728     }
729     else {
730         $RT::Logger->err('System error. right not granted.');
731         return (undef);
732     }
733
734 }
735
736 # }}}
737
738 # {{{ sub CanonicalizeRightName
739
740 =head2 CanonicalizeRightName <RIGHT>
741
742 Takes a queue or system right name in any case and returns it in
743 the correct case. If it's not found, will return undef.
744
745 =cut
746
747 sub CanonicalizeRightName {
748     my $self  = shift;
749     my $right = shift;
750     $right = lc $right;
751     if ( exists $LOWERCASERIGHTNAMES{"$right"} ) {
752         return ( $LOWERCASERIGHTNAMES{"$right"} );
753     }
754     else {
755         return (undef);
756     }
757 }
758
759 # }}}
760
761
762 # {{{ sub Object
763
764 =head2 Object
765
766 If the object this ACE applies to is a queue, returns the queue object. 
767 If the object this ACE applies to is a group, returns the group object. 
768 If it's the system object, returns undef. 
769
770 If the user has no rights, returns undef.
771
772 =cut
773
774
775
776
777 sub Object {
778     my $self = shift;
779
780     my $appliesto_obj;
781
782     if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
783         $appliesto_obj =  $self->__Value('ObjectType')->new($self->CurrentUser);
784         unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
785             return undef;
786         }
787         $appliesto_obj->Load( $self->__Value('ObjectId') );
788         return ($appliesto_obj);
789      }
790     else {
791         $RT::Logger->warning( "$self -> Object called for an object "
792                               . "of an unknown type:"
793                               . $self->ObjectType );
794         return (undef);
795     }
796 }
797
798 # }}}
799
800 # {{{ sub PrincipalObj
801
802 =head2 PrincipalObj
803
804 Returns the RT::Principal object for this ACE. 
805
806 =cut
807
808 sub PrincipalObj {
809     my $self = shift;
810
811     my $princ_obj = RT::Principal->new( $self->CurrentUser );
812     $princ_obj->Load( $self->__Value('PrincipalId') );
813
814     unless ( $princ_obj->Id ) {
815         $RT::Logger->err(
816                    "ACE " . $self->Id . " couldn't load its principal object" );
817     }
818     return ($princ_obj);
819
820 }
821
822 # }}}
823
824 # {{{ ACL related methods
825
826 # {{{ sub _Set
827
828 sub _Set {
829     my $self = shift;
830     return ( 0, $self->loc("ACEs can only be created and deleted.") );
831 }
832
833 # }}}
834
835 # {{{ sub _Value
836
837 sub _Value {
838     my $self = shift;
839
840     if ( $self->__Value('DelegatedBy') eq $self->CurrentUser->PrincipalId ) {
841         return ( $self->__Value(@_) );
842     }
843     elsif ( $self->PrincipalObj->IsGroup
844             && $self->PrincipalObj->Object->HasMemberRecursively(
845                                                 $self->CurrentUser->PrincipalObj
846             )
847       ) {
848         return ( $self->__Value(@_) );
849     }
850     elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
851         return ( $self->__Value(@_) );
852     }
853     else {
854         return undef;
855     }
856 }
857
858 # }}}
859
860
861 # }}}
862
863 # {{{ _CanonicalizePrincipal 
864
865 =head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
866
867 Takes a principal id and a principal type.
868
869 If the principal is a user, resolves it to the proper acl equivalence group.
870 Returns a tuple of  (RT::Principal, PrincipalType)  for the principal we really want to work with
871
872 =cut
873
874 sub _CanonicalizePrincipal {
875     my $self       = shift;
876     my $princ_id   = shift;
877     my $princ_type = shift;
878
879     my $princ_obj = RT::Principal->new($RT::SystemUser);
880     $princ_obj->Load($princ_id);
881
882     unless ( $princ_obj->Id ) {
883         use Carp;
884         $RT::Logger->crit(Carp::cluck);
885         $RT::Logger->crit("Can't load a principal for id $princ_id");
886         return ( $princ_obj, undef );
887     }
888
889     # Rights never get granted to users. they get granted to their 
890     # ACL equivalence groups
891     if ( $princ_type eq 'User' ) {
892         my $equiv_group = RT::Group->new( $self->CurrentUser );
893         $equiv_group->LoadACLEquivalenceGroup($princ_obj);
894         unless ( $equiv_group->Id ) {
895             $RT::Logger->crit(
896                  "No ACL equiv group for princ " . $self->__Value('ObjectId') );
897             return ( 0, $self->loc('System error. Right not granted.') );
898         }
899         $princ_obj  = $equiv_group->PrincipalObj();
900         $princ_type = 'Group';
901
902     }
903     return ( $princ_obj, $princ_type );
904 }
905
906 # }}}
907 1;