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