1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 # <sales@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 = RT::ACE->new($CurrentUser);
66 use base 'RT::Record';
74 require RT::Principals;
80 my (@_ACL_CACHE_HANDLERS);
86 # Queue rights are the sort of queue rights that can only be granted
87 # to real people or groups
91 =head2 LoadByValues PARAMHASH
93 Load an ACE by specifying a paramhash with the following fields:
96 PrincipalType => undef,
112 my %args = ( PrincipalId => undef,
113 PrincipalType => undef,
120 if ( $args{'RightName'} ) {
121 my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
122 unless ( $canonic_name ) {
123 return wantarray ? ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) ) : 0;
125 $args{'RightName'} = $canonic_name;
129 ( $princ_obj, $args{'PrincipalType'} ) =
130 $self->_CanonicalizePrincipal( $args{'PrincipalId'},
131 $args{'PrincipalType'} );
133 unless ( $princ_obj->id ) {
134 return wantarray ? ( 0,
135 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
139 my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args );
141 return wantarray ? ( 0, $self->loc("System error. Right not granted.")) : 0;
144 $self->LoadByCols( PrincipalId => $princ_obj->Id,
145 PrincipalType => $args{'PrincipalType'},
146 RightName => $args{'RightName'},
147 ObjectType => $object_type,
148 ObjectId => $object_id);
150 #If we couldn't load it.
151 unless ( $self->Id ) {
152 return wantarray ? ( 0, $self->loc("ACE not found") ) : 0;
156 return wantarray ? ( $self->Id, $self->loc("Right Loaded") ) : $self->Id;
162 =head2 Create <PARAMS>
164 PARAMS is a parameter hash with the following elements:
166 PrincipalId => The id of an RT::Principal object
167 PrincipalType => "User" "Group" or any Role type
168 RightName => the name of a right. in any case
173 Object => An object to create rights for. ususally, an RT::Queue or RT::Group
174 This should always be a DBIx::SearchBuilder::Record subclass
178 ObjectType => the type of the object in question (ref ($object))
179 ObjectId => the id of the object in question $object->Id
183 Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's false.
192 PrincipalId => undef,
193 PrincipalType => undef,
199 unless ( $args{'RightName'} ) {
200 return ( 0, $self->loc('No right specified') );
203 #if we haven't specified any sort of right, we're talking about a global right
204 if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
205 $args{'Object'} = $RT::System;
207 ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args );
208 unless( $args{'Object'} ) {
209 return ( 0, $self->loc("System error. Right not granted.") );
212 # Validate the principal
214 ( $princ_obj, $args{'PrincipalType'} ) =
215 $self->_CanonicalizePrincipal( $args{'PrincipalId'},
216 $args{'PrincipalType'} );
218 unless ( $princ_obj->id ) {
220 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
228 if (ref( $args{'Object'}) eq 'RT::Group' ) {
229 unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
230 Right => 'AdminGroup' )
232 return ( 0, $self->loc('Permission Denied') );
237 unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
238 return ( 0, $self->loc('Permission Denied') );
243 # Canonicalize and check the right name
244 my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
245 unless ( $canonic_name ) {
246 return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
248 $args{'RightName'} = $canonic_name;
250 #check if it's a valid RightName
251 if ( $args{'Object'}->can('AvailableRights') ) {
252 my $available = $args{'Object'}->AvailableRights($princ_obj);
253 unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
254 $RT::Logger->warning(
255 "Couldn't validate right name '$args{'RightName'}'"
256 ." for object of ". ref( $args{'Object'} ) ." class"
258 return ( 0, $self->loc('Invalid right') );
263 # Make sure the right doesn't already exist.
264 $self->LoadByCols( PrincipalId => $princ_obj->id,
265 PrincipalType => $args{'PrincipalType'},
266 RightName => $args{'RightName'},
267 ObjectType => $args{'ObjectType'},
268 ObjectId => $args{'ObjectId'},
271 return ( 0, $self->loc('[_1] already has that right',
272 $princ_obj->Object->Name) );
275 my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id,
276 PrincipalType => $args{'PrincipalType'},
277 RightName => $args{'RightName'},
278 ObjectType => ref( $args{'Object'} ),
279 ObjectId => $args{'Object'}->id,
283 RT::ACE->InvalidateCaches(
285 RightName => $self->RightName,
288 return ( $id, $self->loc('Right Granted') );
291 return ( 0, $self->loc('System error. Right not granted.') );
297 =head2 Delete { InsideTransaction => undef}
299 Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
300 If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
301 Really, DBIx::SearchBuilder should use and/or fake subtransactions
303 This routine will also recurse and delete any delegations of this right
310 unless ( $self->Id ) {
311 return ( 0, $self->loc('Right not loaded.') );
314 # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
315 # or if it's a delegated ACE and it was delegated by the current user
316 unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) {
317 return ( 0, $self->loc('Permission Denied') );
322 # Helper for Delete with no ACL check
325 my %args = ( InsideTransaction => undef,
328 my $InsideTransaction = $args{'InsideTransaction'};
330 $RT::Handle->BeginTransaction() unless $InsideTransaction;
332 my $right = $self->RightName;
334 my ( $val, $msg ) = $self->SUPER::Delete(@_);
337 RT::ACE->InvalidateCaches( Action => "Revoke", RightName => $right );
338 $RT::Handle->Commit() unless $InsideTransaction;
339 return ( $val, $self->loc('Right revoked') );
342 $RT::Handle->Rollback() unless $InsideTransaction;
343 return ( 0, $self->loc('Right could not be revoked') );
348 =head2 _BootstrapCreate
350 Grant a right with no error checking and no ACL. this is _only_ for
351 installation. If you use this routine without the author's explicit
352 written approval, he will hunt you down and make you spend eternity
353 translating mozilla's code into FORTRAN or intercal.
355 If you think you need this routine, you've mistaken.
359 sub _BootstrapCreate {
363 # When bootstrapping, make sure we get the _right_ users
364 if ( $args{'UserId'} ) {
365 my $user = RT::User->new( $self->CurrentUser );
366 $user->Load( $args{'UserId'} );
367 delete $args{'UserId'};
368 $args{'PrincipalId'} = $user->PrincipalId;
369 $args{'PrincipalType'} = 'User';
372 my $id = $self->SUPER::Create(%args);
378 $RT::Logger->err('System error. right not granted.');
384 =head2 InvalidateCaches
386 Calls any registered ACL cache handlers (see L</RegisterCacheHandler>).
388 Usually called from L</Create> and L</Delete>.
392 sub InvalidateCaches {
395 for my $handler (@_ACL_CACHE_HANDLERS) {
396 next unless ref($handler) eq "CODE";
401 =head2 RegisterCacheHandler
403 Class method. Takes a coderef and adds it to the ACL cache handlers. These
404 handlers are called by L</InvalidateCaches>, usually called itself from
405 L</Create> and L</Delete>.
407 The handlers are passed a hash which may contain any (or none) of these
414 A string indicating the action that (may have) invalidated the cache. Expected
415 values are currently:
425 However, other values may be passed in the future.
429 The (canonicalized) right being granted or revoked.
433 The L<RT::ACE> object just created.
437 Your handler should be flexible enough to account for additional arguments
438 being passed in the future.
442 sub RegisterCacheHandler {
443 push @_ACL_CACHE_HANDLERS, $_[1];
448 my $val = $self->_Value('RightName');
449 return $val unless $val;
451 my $available = $self->Object->AvailableRights;
452 foreach my $right ( keys %$available ) {
453 return $right if $val eq $self->CanonicalizeRightName($right);
456 $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
460 =head2 CanonicalizeRightName <RIGHT>
462 Takes a queue or system right name in any case and returns it in
463 the correct case. If it's not found, will return undef.
467 sub CanonicalizeRightName {
470 for my $class (sort keys %RIGHTS) {
471 return $RIGHTS{$class}{ lc $name }{Name}
472 if $RIGHTS{$class}{ lc $name };
481 If the object this ACE applies to is a queue, returns the queue object.
482 If the object this ACE applies to is a group, returns the group object.
483 If it's the system object, returns undef.
485 If the user has no rights, returns undef.
497 if ($self->__Value('ObjectType') && $self->__Value('ObjectType')->DOES('RT::Record::Role::Rights') ) {
498 $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser);
499 unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
502 $appliesto_obj->Load( $self->__Value('ObjectId') );
503 return ($appliesto_obj);
506 $RT::Logger->warning( "$self -> Object called for an object "
507 . "of an unknown type:"
508 . $self->__Value('ObjectType') );
517 Returns the RT::Principal object for this ACE.
524 my $princ_obj = RT::Principal->new( $self->CurrentUser );
525 $princ_obj->Load( $self->__Value('PrincipalId') );
527 unless ( $princ_obj->Id ) {
529 "ACE " . $self->Id . " couldn't load its principal object" );
540 return ( 0, $self->loc("ACEs can only be created and deleted.") );
548 if ( $self->PrincipalObj->IsGroup
549 && $self->PrincipalObj->Object->HasMemberRecursively(
550 $self->CurrentUser->PrincipalObj
553 return ( $self->__Value(@_) );
555 elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
556 return ( $self->__Value(@_) );
567 =head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
569 Takes a principal id and a principal type.
571 If the principal is a user, resolves it to the proper acl equivalence group.
572 Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with
576 sub _CanonicalizePrincipal {
578 my $princ_id = shift;
579 my $princ_type = shift || '';
581 my $princ_obj = RT::Principal->new(RT->SystemUser);
582 $princ_obj->Load($princ_id);
584 unless ( $princ_obj->Id ) {
586 $RT::Logger->crit(Carp::longmess);
587 $RT::Logger->crit("Can't load a principal for id $princ_id");
588 return ( $princ_obj, undef );
591 # Rights never get granted to users. they get granted to their
592 # ACL equivalence groups
593 if ( $princ_type eq 'User' ) {
594 my $equiv_group = RT::Group->new( $self->CurrentUser );
595 $equiv_group->LoadACLEquivalenceGroup($princ_obj);
596 unless ( $equiv_group->Id ) {
597 $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
598 return ( RT::Principal->new(RT->SystemUser), undef );
600 $princ_obj = $equiv_group->PrincipalObj();
601 $princ_type = 'Group';
604 return ( $princ_obj, $princ_type );
607 sub _ParseObjectArg {
609 my %args = ( Object => undef,
614 if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
615 $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
617 } elsif( $args{'Object'} && ref($args{'Object'}) && !$args{'Object'}->can('id') ) {
618 $RT::Logger->crit( "Method called called Object that has no id method" );
620 } elsif( $args{'Object'} ) {
621 my $obj = $args{'Object'};
622 return ($obj, ref $obj, $obj->id);
623 } elsif ( $args{'ObjectType'} ) {
624 my $obj = $args{'ObjectType'}->new( $self->CurrentUser );
625 $obj->Load( $args{'ObjectId'} );
626 return ($obj, ref $obj, $obj->id);
628 $RT::Logger->crit( "Method called with wrong args" );
640 Returns the current value of id.
641 (In the database, id is stored as int(11).)
649 Returns the current value of PrincipalType.
650 (In the database, PrincipalType is stored as varchar(25).)
654 =head2 SetPrincipalType VALUE
657 Set PrincipalType to VALUE.
658 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
659 (In the database, PrincipalType will be stored as a varchar(25).)
667 Returns the current value of PrincipalId.
668 (In the database, PrincipalId is stored as int(11).)
672 =head2 SetPrincipalId VALUE
675 Set PrincipalId to VALUE.
676 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
677 (In the database, PrincipalId will be stored as a int(11).)
685 Returns the current value of RightName.
686 (In the database, RightName is stored as varchar(25).)
690 =head2 SetRightName VALUE
693 Set RightName to VALUE.
694 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
695 (In the database, RightName will be stored as a varchar(25).)
703 Returns the current value of ObjectType.
704 (In the database, ObjectType is stored as varchar(25).)
708 =head2 SetObjectType VALUE
711 Set ObjectType to VALUE.
712 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
713 (In the database, ObjectType will be stored as a varchar(25).)
721 Returns the current value of ObjectId.
722 (In the database, ObjectId is stored as int(11).)
726 =head2 SetObjectId VALUE
729 Set ObjectId to VALUE.
730 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
731 (In the database, ObjectId will be stored as a int(11).)
739 Returns the current value of Creator.
740 (In the database, Creator is stored as int(11).)
747 Returns the current value of Created.
748 (In the database, Created is stored as datetime.)
755 Returns the current value of LastUpdatedBy.
756 (In the database, LastUpdatedBy is stored as int(11).)
763 Returns the current value of LastUpdated.
764 (In the database, LastUpdated is stored as datetime.)
770 sub _CoreAccessible {
774 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
776 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
778 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
780 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
782 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
784 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
786 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
788 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
790 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
792 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
797 sub FindDependencies {
799 my ($walker, $deps) = @_;
801 $self->SUPER::FindDependencies($walker, $deps);
803 $deps->Add( out => $self->PrincipalObj->Object );
804 $deps->Add( out => $self->Object );
807 RT::Base->_ImportOverlays();