1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
52 my $ace = new RT::ACE($CurrentUser);
68 no warnings qw(redefine);
76 %TICKET_METAPRINCIPALS
80 # {{{ Descriptions of rights
84 # Queue rights are the sort of queue rights that can only be granted
85 # to real people or groups
96 # {{{ Descriptions of principals
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
108 # {{{ sub LoadByValues
110 =head2 LoadByValues PARAMHASH
112 Load an ACE by specifying a paramhash with the following fields:
114 PrincipalId => undef,
115 PrincipalType => undef,
131 my %args = ( PrincipalId => undef,
132 PrincipalType => undef,
139 if ( $args{'RightName'} ) {
140 my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
141 unless ( $canonic_name ) {
142 return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
144 $args{'RightName'} = $canonic_name;
148 ( $princ_obj, $args{'PrincipalType'} ) =
149 $self->_CanonicalizePrincipal( $args{'PrincipalId'},
150 $args{'PrincipalType'} );
152 unless ( $princ_obj->id ) {
154 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
158 my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args );
160 return ( 0, $self->loc("System error. Right not granted.") );
163 $self->LoadByCols( PrincipalId => $princ_obj->Id,
164 PrincipalType => $args{'PrincipalType'},
165 RightName => $args{'RightName'},
166 ObjectType => $object_type,
167 ObjectId => $object_id);
169 #If we couldn't load it.
170 unless ( $self->Id ) {
171 return ( 0, $self->loc("ACE not found") );
175 return ( $self->Id, $self->loc("Right Loaded") );
183 =head2 Create <PARAMS>
185 PARAMS is a parameter hash with the following elements:
187 PrincipalId => The id of an RT::Principal object
188 PrincipalType => "User" "Group" or any Role type
189 RightName => the name of a right. in any case
190 DelegatedBy => The Principal->Id of the user delegating the right
191 DelegatedFrom => The id of the ACE which this new ACE is delegated from
196 Object => An object to create rights for. ususally, an RT::Queue or RT::Group
197 This should always be a DBIx::SearchBuilder::Record subclass
201 ObjectType => the type of the object in question (ref ($object))
202 ObjectId => the id of the object in question $object->Id
206 Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false.
215 PrincipalId => undef,
216 PrincipalType => undef,
222 unless ( $args{'RightName'} ) {
223 return ( 0, $self->loc('No right specified') );
226 #if we haven't specified any sort of right, we're talking about a global right
227 if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
228 $args{'Object'} = $RT::System;
230 ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args );
231 unless( $args{'Object'} ) {
232 return ( 0, $self->loc("System error. Right not granted.") );
235 # {{{ Validate the principal
237 ( $princ_obj, $args{'PrincipalType'} ) =
238 $self->_CanonicalizePrincipal( $args{'PrincipalId'},
239 $args{'PrincipalType'} );
241 unless ( $princ_obj->id ) {
243 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
251 if (ref( $args{'Object'}) eq 'RT::Group' ) {
252 unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
253 Right => 'AdminGroup' )
255 return ( 0, $self->loc('Permission Denied') );
260 unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
261 return ( 0, $self->loc('Permission Denied') );
266 # {{{ Canonicalize and check the right name
267 my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
268 unless ( $canonic_name ) {
269 return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
271 $args{'RightName'} = $canonic_name;
273 #check if it's a valid RightName
274 if ( $args{'Object'}->can('AvailableRights') ) {
275 my $available = $args{'Object'}->AvailableRights;
276 unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
277 $RT::Logger->warning(
278 "Couldn't validate right name '$args{'RightName'}'"
279 ." for object of ". ref( $args{'Object'} ) ." class"
281 return ( 0, $self->loc('Invalid right') );
286 # Make sure the right doesn't already exist.
287 $self->LoadByCols( PrincipalId => $princ_obj->id,
288 PrincipalType => $args{'PrincipalType'},
289 RightName => $args{'RightName'},
290 ObjectType => $args{'ObjectType'},
291 ObjectId => $args{'ObjectId'},
293 DelegatedFrom => 0 );
295 return ( 0, $self->loc('That principal already has that right') );
298 my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id,
299 PrincipalType => $args{'PrincipalType'},
300 RightName => $args{'RightName'},
301 ObjectType => ref( $args{'Object'} ),
302 ObjectId => $args{'Object'}->id,
304 DelegatedFrom => 0 );
306 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
307 RT::Principal->InvalidateACLCache();
310 return ( $id, $self->loc('Right Granted') );
313 return ( 0, $self->loc('System error. Right not granted.') );
321 =head2 Delegate <PARAMS>
323 This routine delegates the current ACE to a principal specified by the
324 B<PrincipalId> parameter.
326 Returns an error if the current user doesn't have the right to be delegated
327 or doesn't have the right to delegate rights.
329 Always returns a tuple of (ReturnValue, Message)
336 my %args = ( PrincipalId => undef,
339 unless ( $self->Id ) {
340 return ( 0, $self->loc("Right not loaded.") );
343 ( $princ_obj, $args{'PrincipalType'} ) =
344 $self->_CanonicalizePrincipal( $args{'PrincipalId'},
345 $args{'PrincipalType'} );
347 unless ( $princ_obj->id ) {
349 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
357 # First, we check to se if the user is delegating rights and
358 # they have the permission to
359 unless ( $self->CurrentUser->HasRight(Right => 'DelegateRights', Object => $self->Object) ) {
360 return ( 0, $self->loc("Permission Denied") );
363 unless ( $self->PrincipalObj->IsGroup ) {
364 return ( 0, $self->loc("System Error") );
366 unless ( $self->PrincipalObj->Object->HasMemberRecursively(
367 $self->CurrentUser->PrincipalObj
370 return ( 0, $self->loc("Permission Denied") );
375 my $concurrency_check = RT::ACE->new($RT::SystemUser);
376 $concurrency_check->Load( $self->Id );
377 unless ( $concurrency_check->Id ) {
379 "Trying to delegate a right which had already been deleted");
380 return ( 0, $self->loc('Permission Denied') );
383 my $delegated_ace = RT::ACE->new( $self->CurrentUser );
385 # Make sure the right doesn't already exist.
386 $delegated_ace->LoadByCols( PrincipalId => $princ_obj->Id,
387 PrincipalType => 'Group',
388 RightName => $self->__Value('RightName'),
389 ObjectType => $self->__Value('ObjectType'),
390 ObjectId => $self->__Value('ObjectId'),
391 DelegatedBy => $self->CurrentUser->PrincipalId,
392 DelegatedFrom => $self->id );
393 if ( $delegated_ace->Id ) {
394 return ( 0, $self->loc('That principal already has that right') );
396 my $id = $delegated_ace->SUPER::Create(
397 PrincipalId => $princ_obj->Id,
398 PrincipalType => 'Group', # do we want to hardcode this?
399 RightName => $self->__Value('RightName'),
400 ObjectType => $self->__Value('ObjectType'),
401 ObjectId => $self->__Value('ObjectId'),
402 DelegatedBy => $self->CurrentUser->PrincipalId,
403 DelegatedFrom => $self->id );
405 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
406 # TODO what about the groups key cache?
407 RT::Principal->InvalidateACLCache();
410 return ( $id, $self->loc('Right Delegated') );
413 return ( 0, $self->loc('System error. Right not delegated.') );
421 =head2 Delete { InsideTransaction => undef}
423 Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
424 If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
425 Really, DBIx::SearchBuilder should use and/or fake subtransactions
427 This routine will also recurse and delete any delegations of this right
434 unless ( $self->Id ) {
435 return ( 0, $self->loc('Right not loaded.') );
438 # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
439 # or if it's a delegated ACE and it was delegated by the current user
441 ( $self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)
442 && $self->__Value('DelegatedBy') == 0 )
443 || ( $self->__Value('DelegatedBy') == $self->CurrentUser->PrincipalId )
445 return ( 0, $self->loc('Permission Denied') );
450 # Helper for Delete with no ACL check
453 my %args = ( InsideTransaction => undef,
456 my $InsideTransaction = $args{'InsideTransaction'};
458 $RT::Handle->BeginTransaction() unless $InsideTransaction;
460 my $delegated_from_this = RT::ACL->new($RT::SystemUser);
461 $delegated_from_this->Limit( FIELD => 'DelegatedFrom',
463 VALUE => $self->Id );
465 my $delete_succeeded = 1;
467 while ( my $delegated_ace = $delegated_from_this->Next ) {
468 ( $delete_succeeded, $submsg ) =
469 $delegated_ace->_Delete( InsideTransaction => 1 );
470 last unless ($delete_succeeded);
473 unless ($delete_succeeded) {
474 $RT::Handle->Rollback() unless $InsideTransaction;
475 return ( 0, $self->loc('Right could not be revoked') );
478 my ( $val, $msg ) = $self->SUPER::Delete(@_);
480 # If we're revoking delegation rights (see above), we may need to
481 # revoke all rights delegated by the recipient.
482 if ($val and ($self->RightName() eq 'DelegateRights' or
483 $self->RightName() eq 'SuperUser')) {
484 $val = $self->PrincipalObj->_CleanupInvalidDelegations( InsideTransaction => 1 );
488 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
489 # TODO what about the groups key cache?
490 RT::Principal->InvalidateACLCache();
491 $RT::Handle->Commit() unless $InsideTransaction;
492 return ( $val, $self->loc('Right revoked') );
495 $RT::Handle->Rollback() unless $InsideTransaction;
496 return ( 0, $self->loc('Right could not be revoked') );
501 # {{{ sub _BootstrapCreate
503 =head2 _BootstrapCreate
505 Grant a right with no error checking and no ACL. this is _only_ for
506 installation. If you use this routine without the author's explicit
507 written approval, he will hunt you down and make you spend eternity
508 translating mozilla's code into FORTRAN or intercal.
510 If you think you need this routine, you've mistaken.
514 sub _BootstrapCreate {
518 # When bootstrapping, make sure we get the _right_ users
519 if ( $args{'UserId'} ) {
520 my $user = RT::User->new( $self->CurrentUser );
521 $user->Load( $args{'UserId'} );
522 delete $args{'UserId'};
523 $args{'PrincipalId'} = $user->PrincipalId;
524 $args{'PrincipalType'} = 'User';
527 my $id = $self->SUPER::Create(%args);
533 $RT::Logger->err('System error. right not granted.');
541 # {{{ sub CanonicalizeRightName
545 my $val = $self->_Value('RightName');
546 return $val unless $val;
548 my $available = $self->Object->AvailableRights;
549 foreach my $right ( keys %$available ) {
550 return $right if $val eq $self->CanonicalizeRightName($right);
553 $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
557 =head2 CanonicalizeRightName <RIGHT>
559 Takes a queue or system right name in any case and returns it in
560 the correct case. If it's not found, will return undef.
564 sub CanonicalizeRightName {
566 return $LOWERCASERIGHTNAMES{ lc shift };
576 If the object this ACE applies to is a queue, returns the queue object.
577 If the object this ACE applies to is a group, returns the group object.
578 If it's the system object, returns undef.
580 If the user has no rights, returns undef.
592 if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
593 $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser);
594 unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
597 $appliesto_obj->Load( $self->__Value('ObjectId') );
598 return ($appliesto_obj);
601 $RT::Logger->warning( "$self -> Object called for an object "
602 . "of an unknown type:"
603 . $self->__Value('ObjectType') );
610 # {{{ sub PrincipalObj
614 Returns the RT::Principal object for this ACE.
621 my $princ_obj = RT::Principal->new( $self->CurrentUser );
622 $princ_obj->Load( $self->__Value('PrincipalId') );
624 unless ( $princ_obj->Id ) {
626 "ACE " . $self->Id . " couldn't load its principal object" );
634 # {{{ ACL related methods
640 return ( 0, $self->loc("ACEs can only be created and deleted.") );
650 if ( $self->__Value('DelegatedBy') eq $self->CurrentUser->PrincipalId ) {
651 return ( $self->__Value(@_) );
653 elsif ( $self->PrincipalObj->IsGroup
654 && $self->PrincipalObj->Object->HasMemberRecursively(
655 $self->CurrentUser->PrincipalObj
658 return ( $self->__Value(@_) );
660 elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
661 return ( $self->__Value(@_) );
673 # {{{ _CanonicalizePrincipal
675 =head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
677 Takes a principal id and a principal type.
679 If the principal is a user, resolves it to the proper acl equivalence group.
680 Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with
684 sub _CanonicalizePrincipal {
686 my $princ_id = shift;
687 my $princ_type = shift || '';
689 my $princ_obj = RT::Principal->new($RT::SystemUser);
690 $princ_obj->Load($princ_id);
692 unless ( $princ_obj->Id ) {
694 $RT::Logger->crit(Carp::cluck);
695 $RT::Logger->crit("Can't load a principal for id $princ_id");
696 return ( $princ_obj, undef );
699 # Rights never get granted to users. they get granted to their
700 # ACL equivalence groups
701 if ( $princ_type eq 'User' ) {
702 my $equiv_group = RT::Group->new( $self->CurrentUser );
703 $equiv_group->LoadACLEquivalenceGroup($princ_obj);
704 unless ( $equiv_group->Id ) {
705 $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
706 return ( RT::Principal->new($RT::SystemUser), undef );
708 $princ_obj = $equiv_group->PrincipalObj();
709 $princ_type = 'Group';
712 return ( $princ_obj, $princ_type );
715 sub _ParseObjectArg {
717 my %args = ( Object => undef,
722 if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
723 $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
725 } elsif( $args{'Object'} && !UNIVERSAL::can($args{'Object'},'id') ) {
726 $RT::Logger->crit( "Method called called Object that has no id method" );
728 } elsif( $args{'Object'} ) {
729 my $obj = $args{'Object'};
730 return ($obj, ref $obj, $obj->id);
731 } elsif ( $args{'ObjectType'} ) {
732 my $obj = $args{'ObjectType'}->new( $self->CurrentUser );
733 $obj->Load( $args{'ObjectId'} );
734 return ($obj, ref $obj, $obj->id);
736 $RT::Logger->crit( "Method called with wrong args" );