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