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