Initial commit 4.0.5-3
[usit-rt.git] / lib / RT / ACE.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6 #                                          <sales@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/licenses/old-licenses/gpl-2.0.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
49 =head1 SYNOPSIS
50
51   use RT::ACE;
52   my $ace = RT::ACE->new($CurrentUser);
53
54
55 =head1 DESCRIPTION
56
57
58
59 =head1 METHODS
60
61
62 =cut
63
64
65 package RT::ACE;
66 use base 'RT::Record';
67
68 sub Table {'ACL'}
69
70
71 use strict;
72 use warnings;
73
74 use RT::Principals;
75 use RT::Queues;
76 use RT::Groups;
77
78 use vars qw (
79   %LOWERCASERIGHTNAMES
80   %OBJECT_TYPES
81   %TICKET_METAPRINCIPALS
82 );
83
84
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 =cut
92
93
94
95
96
97
98 %TICKET_METAPRINCIPALS = (
99     Owner     => 'The owner of a ticket',                             # loc_pair
100     Requestor => 'The requestor of a ticket',                         # loc_pair
101     Cc        => 'The CC of a ticket',                                # loc_pair
102     AdminCc   => 'The administrative CC of a ticket',                 # loc_pair
103 );
104
105
106
107
108 =head2 LoadByValues PARAMHASH
109
110 Load an ACE by specifying a paramhash with the following fields:
111
112               PrincipalId => undef,
113               PrincipalType => undef,
114               RightName => undef,
115
116         And either:
117
118               Object => undef,
119
120             OR
121
122               ObjectType => undef,
123               ObjectId => undef
124
125 =cut
126
127 sub LoadByValues {
128     my $self = shift;
129     my %args = ( PrincipalId   => undef,
130                  PrincipalType => undef,
131                  RightName     => undef,
132                  Object    => undef,
133                  ObjectId    => undef,
134                  ObjectType    => undef,
135                  @_ );
136
137     if ( $args{'RightName'} ) {
138         my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
139         unless ( $canonic_name ) {
140             return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
141         }
142         $args{'RightName'} = $canonic_name;
143     }
144
145     my $princ_obj;
146     ( $princ_obj, $args{'PrincipalType'} ) =
147       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
148                                      $args{'PrincipalType'} );
149
150     unless ( $princ_obj->id ) {
151         return ( 0,
152                  $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
153         );
154     }
155
156     my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args );
157     unless( $object ) {
158         return ( 0, $self->loc("System error. Right not granted.") );
159     }
160
161     $self->LoadByCols( PrincipalId   => $princ_obj->Id,
162                        PrincipalType => $args{'PrincipalType'},
163                        RightName     => $args{'RightName'},
164                        ObjectType    => $object_type,
165                        ObjectId      => $object_id);
166
167     #If we couldn't load it.
168     unless ( $self->Id ) {
169         return ( 0, $self->loc("ACE not found") );
170     }
171
172     # if we could
173     return ( $self->Id, $self->loc("Right Loaded") );
174
175 }
176
177
178
179 =head2 Create <PARAMS>
180
181 PARAMS is a parameter hash with the following elements:
182
183    PrincipalId => The id of an RT::Principal object
184    PrincipalType => "User" "Group" or any Role type
185    RightName => the name of a right. in any case
186
187
188     Either:
189
190    Object => An object to create rights for. ususally, an RT::Queue or RT::Group
191              This should always be a DBIx::SearchBuilder::Record subclass
192
193         OR
194
195    ObjectType => the type of the object in question (ref ($object))
196    ObjectId => the id of the object in question $object->Id
197
198
199
200    Returns a tuple of (STATUS, MESSAGE);  If the call succeeded, STATUS is true. Otherwise it's false.
201
202
203
204 =cut
205
206 sub Create {
207     my $self = shift;
208     my %args = (
209         PrincipalId   => undef,
210         PrincipalType => undef,
211         RightName     => undef,
212         Object        => undef,
213         @_
214     );
215
216     unless ( $args{'RightName'} ) {
217         return ( 0, $self->loc('No right specified') );
218     }
219
220     #if we haven't specified any sort of right, we're talking about a global right
221     if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
222         $args{'Object'} = $RT::System;
223     }
224     ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args );
225     unless( $args{'Object'} ) {
226         return ( 0, $self->loc("System error. Right not granted.") );
227     }
228
229     # Validate the principal
230     my $princ_obj;
231     ( $princ_obj, $args{'PrincipalType'} ) =
232       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
233                                      $args{'PrincipalType'} );
234
235     unless ( $princ_obj->id ) {
236         return ( 0,
237                  $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
238         );
239     }
240
241     # }}}
242
243     # Check the ACL
244
245     if (ref( $args{'Object'}) eq 'RT::Group' ) {
246         unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
247                                                   Right => 'AdminGroup' )
248           ) {
249             return ( 0, $self->loc('Permission Denied') );
250         }
251     }
252
253     else {
254         unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
255             return ( 0, $self->loc('Permission Denied') );
256         }
257     }
258     # }}}
259
260     # Canonicalize and check the right name
261     my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
262     unless ( $canonic_name ) {
263         return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
264     }
265     $args{'RightName'} = $canonic_name;
266
267     #check if it's a valid RightName
268     if ( $args{'Object'}->can('AvailableRights') ) {
269         my $available = $args{'Object'}->AvailableRights;
270         unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
271             $RT::Logger->warning(
272                 "Couldn't validate right name '$args{'RightName'}'"
273                 ." for object of ". ref( $args{'Object'} ) ." class"
274             );
275             return ( 0, $self->loc('Invalid right') );
276         }
277     }
278     # }}}
279
280     # Make sure the right doesn't already exist.
281     $self->LoadByCols( PrincipalId   => $princ_obj->id,
282                        PrincipalType => $args{'PrincipalType'},
283                        RightName     => $args{'RightName'},
284                        ObjectType    => $args{'ObjectType'},
285                        ObjectId      => $args{'ObjectId'},
286                    );
287     if ( $self->Id ) {
288         return ( 0, $self->loc('That principal already has that right') );
289     }
290
291     my $id = $self->SUPER::Create( PrincipalId   => $princ_obj->id,
292                                    PrincipalType => $args{'PrincipalType'},
293                                    RightName     => $args{'RightName'},
294                                    ObjectType    => ref( $args{'Object'} ),
295                                    ObjectId      => $args{'Object'}->id,
296                                );
297
298     #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
299     RT::Principal->InvalidateACLCache();
300
301     if ( $id ) {
302         return ( $id, $self->loc('Right Granted') );
303     }
304     else {
305         return ( 0, $self->loc('System error. Right not granted.') );
306     }
307 }
308
309
310
311 =head2 Delete { InsideTransaction => undef}
312
313 Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
314 If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
315 Really, DBIx::SearchBuilder should use and/or fake subtransactions
316
317 This routine will also recurse and delete any delegations of this right
318
319 =cut
320
321 sub Delete {
322     my $self = shift;
323
324     unless ( $self->Id ) {
325         return ( 0, $self->loc('Right not loaded.') );
326     }
327
328     # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
329     # or if it's a delegated ACE and it was delegated by the current user
330     unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) {
331         return ( 0, $self->loc('Permission Denied') );
332     }
333     $self->_Delete(@_);
334 }
335
336 # Helper for Delete with no ACL check
337 sub _Delete {
338     my $self = shift;
339     my %args = ( InsideTransaction => undef,
340                  @_ );
341
342     my $InsideTransaction = $args{'InsideTransaction'};
343
344     $RT::Handle->BeginTransaction() unless $InsideTransaction;
345
346     my ( $val, $msg ) = $self->SUPER::Delete(@_);
347
348     if ($val) {
349         #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
350         # TODO what about the groups key cache?
351         RT::Principal->InvalidateACLCache();
352         $RT::Handle->Commit() unless $InsideTransaction;
353         return ( $val, $self->loc('Right revoked') );
354     }
355
356     $RT::Handle->Rollback() unless $InsideTransaction;
357     return ( 0, $self->loc('Right could not be revoked') );
358 }
359
360
361
362 =head2 _BootstrapCreate
363
364 Grant a right with no error checking and no ACL. this is _only_ for 
365 installation. If you use this routine without the author's explicit 
366 written approval, he will hunt you down and make you spend eternity
367 translating mozilla's code into FORTRAN or intercal.
368
369 If you think you need this routine, you've mistaken. 
370
371 =cut
372
373 sub _BootstrapCreate {
374     my $self = shift;
375     my %args = (@_);
376
377     # When bootstrapping, make sure we get the _right_ users
378     if ( $args{'UserId'} ) {
379         my $user = RT::User->new( $self->CurrentUser );
380         $user->Load( $args{'UserId'} );
381         delete $args{'UserId'};
382         $args{'PrincipalId'}   = $user->PrincipalId;
383         $args{'PrincipalType'} = 'User';
384     }
385
386     my $id = $self->SUPER::Create(%args);
387
388     if ( $id > 0 ) {
389         return ($id);
390     }
391     else {
392         $RT::Logger->err('System error. right not granted.');
393         return (undef);
394     }
395
396 }
397
398
399
400 sub RightName {
401     my $self = shift;
402     my $val = $self->_Value('RightName');
403     return $val unless $val;
404
405     my $available = $self->Object->AvailableRights;
406     foreach my $right ( keys %$available ) {
407         return $right if $val eq $self->CanonicalizeRightName($right);
408     }
409
410     $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
411     return $val;
412 }
413
414 =head2 CanonicalizeRightName <RIGHT>
415
416 Takes a queue or system right name in any case and returns it in
417 the correct case. If it's not found, will return undef.
418
419 =cut
420
421 sub CanonicalizeRightName {
422     my $self  = shift;
423     return $LOWERCASERIGHTNAMES{ lc shift };
424 }
425
426
427
428
429 =head2 Object
430
431 If the object this ACE applies to is a queue, returns the queue object. 
432 If the object this ACE applies to is a group, returns the group object. 
433 If it's the system object, returns undef. 
434
435 If the user has no rights, returns undef.
436
437 =cut
438
439
440
441
442 sub Object {
443     my $self = shift;
444
445     my $appliesto_obj;
446
447     if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
448         $appliesto_obj =  $self->__Value('ObjectType')->new($self->CurrentUser);
449         unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
450             return undef;
451         }
452         $appliesto_obj->Load( $self->__Value('ObjectId') );
453         return ($appliesto_obj);
454      }
455     else {
456         $RT::Logger->warning( "$self -> Object called for an object "
457                               . "of an unknown type:"
458                               . $self->__Value('ObjectType') );
459         return (undef);
460     }
461 }
462
463
464
465 =head2 PrincipalObj
466
467 Returns the RT::Principal object for this ACE. 
468
469 =cut
470
471 sub PrincipalObj {
472     my $self = shift;
473
474     my $princ_obj = RT::Principal->new( $self->CurrentUser );
475     $princ_obj->Load( $self->__Value('PrincipalId') );
476
477     unless ( $princ_obj->Id ) {
478         $RT::Logger->err(
479                    "ACE " . $self->Id . " couldn't load its principal object" );
480     }
481     return ($princ_obj);
482
483 }
484
485
486
487
488 sub _Set {
489     my $self = shift;
490     return ( 0, $self->loc("ACEs can only be created and deleted.") );
491 }
492
493
494
495 sub _Value {
496     my $self = shift;
497
498     if ( $self->PrincipalObj->IsGroup
499             && $self->PrincipalObj->Object->HasMemberRecursively(
500                                                 $self->CurrentUser->PrincipalObj
501             )
502       ) {
503         return ( $self->__Value(@_) );
504     }
505     elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
506         return ( $self->__Value(@_) );
507     }
508     else {
509         return undef;
510     }
511 }
512
513
514
515
516
517 =head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
518
519 Takes a principal id and a principal type.
520
521 If the principal is a user, resolves it to the proper acl equivalence group.
522 Returns a tuple of  (RT::Principal, PrincipalType)  for the principal we really want to work with
523
524 =cut
525
526 sub _CanonicalizePrincipal {
527     my $self       = shift;
528     my $princ_id   = shift;
529     my $princ_type = shift || '';
530
531     my $princ_obj = RT::Principal->new(RT->SystemUser);
532     $princ_obj->Load($princ_id);
533
534     unless ( $princ_obj->Id ) {
535         use Carp;
536         $RT::Logger->crit(Carp::longmess);
537         $RT::Logger->crit("Can't load a principal for id $princ_id");
538         return ( $princ_obj, undef );
539     }
540
541     # Rights never get granted to users. they get granted to their 
542     # ACL equivalence groups
543     if ( $princ_type eq 'User' ) {
544         my $equiv_group = RT::Group->new( $self->CurrentUser );
545         $equiv_group->LoadACLEquivalenceGroup($princ_obj);
546         unless ( $equiv_group->Id ) {
547             $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
548             return ( RT::Principal->new(RT->SystemUser), undef );
549         }
550         $princ_obj  = $equiv_group->PrincipalObj();
551         $princ_type = 'Group';
552
553     }
554     return ( $princ_obj, $princ_type );
555 }
556
557 sub _ParseObjectArg {
558     my $self = shift;
559     my %args = ( Object    => undef,
560                  ObjectId    => undef,
561                  ObjectType    => undef,
562                  @_ );
563
564     if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
565         $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
566         return ();
567     } elsif( $args{'Object'} && ref($args{'Object'}) &&  !$args{'Object'}->can('id') ) {
568         $RT::Logger->crit( "Method called called Object that has no id method" );
569         return ();
570     } elsif( $args{'Object'} ) {
571         my $obj = $args{'Object'};
572         return ($obj, ref $obj, $obj->id);
573     } elsif ( $args{'ObjectType'} ) {
574         my $obj =  $args{'ObjectType'}->new( $self->CurrentUser );
575         $obj->Load( $args{'ObjectId'} );
576         return ($obj, ref $obj, $obj->id);
577     } else {
578         $RT::Logger->crit( "Method called with wrong args" );
579         return ();
580     }
581 }
582
583
584 # }}}
585
586
587
588 =head2 id
589
590 Returns the current value of id.
591 (In the database, id is stored as int(11).)
592
593
594 =cut
595
596
597 =head2 PrincipalType
598
599 Returns the current value of PrincipalType.
600 (In the database, PrincipalType is stored as varchar(25).)
601
602
603
604 =head2 SetPrincipalType VALUE
605
606
607 Set PrincipalType to VALUE.
608 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
609 (In the database, PrincipalType will be stored as a varchar(25).)
610
611
612 =cut
613
614
615 =head2 PrincipalId
616
617 Returns the current value of PrincipalId.
618 (In the database, PrincipalId is stored as int(11).)
619
620
621
622 =head2 SetPrincipalId VALUE
623
624
625 Set PrincipalId to VALUE.
626 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
627 (In the database, PrincipalId will be stored as a int(11).)
628
629
630 =cut
631
632
633 =head2 RightName
634
635 Returns the current value of RightName.
636 (In the database, RightName is stored as varchar(25).)
637
638
639
640 =head2 SetRightName VALUE
641
642
643 Set RightName to VALUE.
644 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
645 (In the database, RightName will be stored as a varchar(25).)
646
647
648 =cut
649
650
651 =head2 ObjectType
652
653 Returns the current value of ObjectType.
654 (In the database, ObjectType is stored as varchar(25).)
655
656
657
658 =head2 SetObjectType VALUE
659
660
661 Set ObjectType to VALUE.
662 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
663 (In the database, ObjectType will be stored as a varchar(25).)
664
665
666 =cut
667
668
669 =head2 ObjectId
670
671 Returns the current value of ObjectId.
672 (In the database, ObjectId is stored as int(11).)
673
674
675
676 =head2 SetObjectId VALUE
677
678
679 Set ObjectId to VALUE.
680 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
681 (In the database, ObjectId will be stored as a int(11).)
682
683
684 =cut
685
686
687 =head2 Creator
688
689 Returns the current value of Creator.
690 (In the database, Creator is stored as int(11).)
691
692 =cut
693
694
695 =head2 Created
696
697 Returns the current value of Created.
698 (In the database, Created is stored as datetime.)
699
700 =cut
701
702
703 =head2 LastUpdatedBy
704
705 Returns the current value of LastUpdatedBy.
706 (In the database, LastUpdatedBy is stored as int(11).)
707
708 =cut
709
710
711 =head2 LastUpdated
712
713 Returns the current value of LastUpdated.
714 (In the database, LastUpdated is stored as datetime.)
715
716 =cut
717
718
719
720 sub _CoreAccessible {
721     {
722
723         id =>
724                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
725         PrincipalType =>
726                 {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
727         PrincipalId =>
728                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
729         RightName =>
730                 {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
731         ObjectType =>
732                 {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
733         ObjectId =>
734                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
735         Creator =>
736                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
737         Created =>
738                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
739         LastUpdatedBy =>
740                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
741         LastUpdated =>
742                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
743
744  }
745 };
746
747 RT::Base->_ImportOverlays();
748
749 1;