Upgrade to 4.0.10.
[usit-rt.git] / lib / RT / ACE.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
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
65package RT::ACE;
66use base 'RT::Record';
67
68sub Table {'ACL'}
69
70
71use strict;
72use warnings;
73
74use RT::Principals;
75use RT::Queues;
76use RT::Groups;
77
78use 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
110Load 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
127sub 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
181PARAMS 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
206sub 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 ) {
403d7b0b
MKG
288 return ( 0, $self->loc('[_1] already has that right',
289 $princ_obj->Object->Name) );
84fb5b46
MKG
290 }
291
292 my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id,
293 PrincipalType => $args{'PrincipalType'},
294 RightName => $args{'RightName'},
295 ObjectType => ref( $args{'Object'} ),
296 ObjectId => $args{'Object'}->id,
297 );
298
299 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
300 RT::Principal->InvalidateACLCache();
301
302 if ( $id ) {
303 return ( $id, $self->loc('Right Granted') );
304 }
305 else {
306 return ( 0, $self->loc('System error. Right not granted.') );
307 }
308}
309
310
311
312=head2 Delete { InsideTransaction => undef}
313
314Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
315If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
316Really, DBIx::SearchBuilder should use and/or fake subtransactions
317
318This routine will also recurse and delete any delegations of this right
319
320=cut
321
322sub Delete {
323 my $self = shift;
324
325 unless ( $self->Id ) {
326 return ( 0, $self->loc('Right not loaded.') );
327 }
328
329 # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
330 # or if it's a delegated ACE and it was delegated by the current user
331 unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) {
332 return ( 0, $self->loc('Permission Denied') );
333 }
334 $self->_Delete(@_);
335}
336
337# Helper for Delete with no ACL check
338sub _Delete {
339 my $self = shift;
340 my %args = ( InsideTransaction => undef,
341 @_ );
342
343 my $InsideTransaction = $args{'InsideTransaction'};
344
345 $RT::Handle->BeginTransaction() unless $InsideTransaction;
346
347 my ( $val, $msg ) = $self->SUPER::Delete(@_);
348
349 if ($val) {
350 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
351 # TODO what about the groups key cache?
352 RT::Principal->InvalidateACLCache();
353 $RT::Handle->Commit() unless $InsideTransaction;
354 return ( $val, $self->loc('Right revoked') );
355 }
356
357 $RT::Handle->Rollback() unless $InsideTransaction;
358 return ( 0, $self->loc('Right could not be revoked') );
359}
360
361
362
363=head2 _BootstrapCreate
364
365Grant a right with no error checking and no ACL. this is _only_ for
366installation. If you use this routine without the author's explicit
367written approval, he will hunt you down and make you spend eternity
368translating mozilla's code into FORTRAN or intercal.
369
370If you think you need this routine, you've mistaken.
371
372=cut
373
374sub _BootstrapCreate {
375 my $self = shift;
376 my %args = (@_);
377
378 # When bootstrapping, make sure we get the _right_ users
379 if ( $args{'UserId'} ) {
380 my $user = RT::User->new( $self->CurrentUser );
381 $user->Load( $args{'UserId'} );
382 delete $args{'UserId'};
383 $args{'PrincipalId'} = $user->PrincipalId;
384 $args{'PrincipalType'} = 'User';
385 }
386
387 my $id = $self->SUPER::Create(%args);
388
389 if ( $id > 0 ) {
390 return ($id);
391 }
392 else {
393 $RT::Logger->err('System error. right not granted.');
394 return (undef);
395 }
396
397}
398
399
400
401sub RightName {
402 my $self = shift;
403 my $val = $self->_Value('RightName');
404 return $val unless $val;
405
406 my $available = $self->Object->AvailableRights;
407 foreach my $right ( keys %$available ) {
408 return $right if $val eq $self->CanonicalizeRightName($right);
409 }
410
411 $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
412 return $val;
413}
414
415=head2 CanonicalizeRightName <RIGHT>
416
417Takes a queue or system right name in any case and returns it in
418the correct case. If it's not found, will return undef.
419
420=cut
421
422sub CanonicalizeRightName {
423 my $self = shift;
424 return $LOWERCASERIGHTNAMES{ lc shift };
425}
426
427
428
429
430=head2 Object
431
432If the object this ACE applies to is a queue, returns the queue object.
433If the object this ACE applies to is a group, returns the group object.
434If it's the system object, returns undef.
435
436If the user has no rights, returns undef.
437
438=cut
439
440
441
442
443sub Object {
444 my $self = shift;
445
446 my $appliesto_obj;
447
448 if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
449 $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser);
450 unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
451 return undef;
452 }
453 $appliesto_obj->Load( $self->__Value('ObjectId') );
454 return ($appliesto_obj);
455 }
456 else {
457 $RT::Logger->warning( "$self -> Object called for an object "
458 . "of an unknown type:"
459 . $self->__Value('ObjectType') );
460 return (undef);
461 }
462}
463
464
465
466=head2 PrincipalObj
467
468Returns the RT::Principal object for this ACE.
469
470=cut
471
472sub PrincipalObj {
473 my $self = shift;
474
475 my $princ_obj = RT::Principal->new( $self->CurrentUser );
476 $princ_obj->Load( $self->__Value('PrincipalId') );
477
478 unless ( $princ_obj->Id ) {
479 $RT::Logger->err(
480 "ACE " . $self->Id . " couldn't load its principal object" );
481 }
482 return ($princ_obj);
483
484}
485
486
487
488
489sub _Set {
490 my $self = shift;
491 return ( 0, $self->loc("ACEs can only be created and deleted.") );
492}
493
494
495
496sub _Value {
497 my $self = shift;
498
499 if ( $self->PrincipalObj->IsGroup
500 && $self->PrincipalObj->Object->HasMemberRecursively(
501 $self->CurrentUser->PrincipalObj
502 )
503 ) {
504 return ( $self->__Value(@_) );
505 }
506 elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
507 return ( $self->__Value(@_) );
508 }
509 else {
510 return undef;
511 }
512}
513
514
515
516
517
518=head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
519
520Takes a principal id and a principal type.
521
522If the principal is a user, resolves it to the proper acl equivalence group.
523Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with
524
525=cut
526
527sub _CanonicalizePrincipal {
528 my $self = shift;
529 my $princ_id = shift;
530 my $princ_type = shift || '';
531
532 my $princ_obj = RT::Principal->new(RT->SystemUser);
533 $princ_obj->Load($princ_id);
534
535 unless ( $princ_obj->Id ) {
536 use Carp;
537 $RT::Logger->crit(Carp::longmess);
538 $RT::Logger->crit("Can't load a principal for id $princ_id");
539 return ( $princ_obj, undef );
540 }
541
542 # Rights never get granted to users. they get granted to their
543 # ACL equivalence groups
544 if ( $princ_type eq 'User' ) {
545 my $equiv_group = RT::Group->new( $self->CurrentUser );
546 $equiv_group->LoadACLEquivalenceGroup($princ_obj);
547 unless ( $equiv_group->Id ) {
548 $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
549 return ( RT::Principal->new(RT->SystemUser), undef );
550 }
551 $princ_obj = $equiv_group->PrincipalObj();
552 $princ_type = 'Group';
553
554 }
555 return ( $princ_obj, $princ_type );
556}
557
558sub _ParseObjectArg {
559 my $self = shift;
560 my %args = ( Object => undef,
561 ObjectId => undef,
562 ObjectType => undef,
563 @_ );
564
565 if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
566 $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
567 return ();
568 } elsif( $args{'Object'} && ref($args{'Object'}) && !$args{'Object'}->can('id') ) {
569 $RT::Logger->crit( "Method called called Object that has no id method" );
570 return ();
571 } elsif( $args{'Object'} ) {
572 my $obj = $args{'Object'};
573 return ($obj, ref $obj, $obj->id);
574 } elsif ( $args{'ObjectType'} ) {
575 my $obj = $args{'ObjectType'}->new( $self->CurrentUser );
576 $obj->Load( $args{'ObjectId'} );
577 return ($obj, ref $obj, $obj->id);
578 } else {
579 $RT::Logger->crit( "Method called with wrong args" );
580 return ();
581 }
582}
583
584
585# }}}
586
587
588
589=head2 id
590
591Returns the current value of id.
592(In the database, id is stored as int(11).)
593
594
595=cut
596
597
598=head2 PrincipalType
599
600Returns the current value of PrincipalType.
601(In the database, PrincipalType is stored as varchar(25).)
602
603
604
605=head2 SetPrincipalType VALUE
606
607
608Set PrincipalType to VALUE.
609Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
610(In the database, PrincipalType will be stored as a varchar(25).)
611
612
613=cut
614
615
616=head2 PrincipalId
617
618Returns the current value of PrincipalId.
619(In the database, PrincipalId is stored as int(11).)
620
621
622
623=head2 SetPrincipalId VALUE
624
625
626Set PrincipalId to VALUE.
627Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
628(In the database, PrincipalId will be stored as a int(11).)
629
630
631=cut
632
633
634=head2 RightName
635
636Returns the current value of RightName.
637(In the database, RightName is stored as varchar(25).)
638
639
640
641=head2 SetRightName VALUE
642
643
644Set RightName to VALUE.
645Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
646(In the database, RightName will be stored as a varchar(25).)
647
648
649=cut
650
651
652=head2 ObjectType
653
654Returns the current value of ObjectType.
655(In the database, ObjectType is stored as varchar(25).)
656
657
658
659=head2 SetObjectType VALUE
660
661
662Set ObjectType to VALUE.
663Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
664(In the database, ObjectType will be stored as a varchar(25).)
665
666
667=cut
668
669
670=head2 ObjectId
671
672Returns the current value of ObjectId.
673(In the database, ObjectId is stored as int(11).)
674
675
676
677=head2 SetObjectId VALUE
678
679
680Set ObjectId to VALUE.
681Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
682(In the database, ObjectId will be stored as a int(11).)
683
684
685=cut
686
687
688=head2 Creator
689
690Returns the current value of Creator.
691(In the database, Creator is stored as int(11).)
692
693=cut
694
695
696=head2 Created
697
698Returns the current value of Created.
699(In the database, Created is stored as datetime.)
700
701=cut
702
703
704=head2 LastUpdatedBy
705
706Returns the current value of LastUpdatedBy.
707(In the database, LastUpdatedBy is stored as int(11).)
708
709=cut
710
711
712=head2 LastUpdated
713
714Returns the current value of LastUpdated.
715(In the database, LastUpdated is stored as datetime.)
716
717=cut
718
719
720
721sub _CoreAccessible {
722 {
723
724 id =>
725 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
726 PrincipalType =>
727 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
728 PrincipalId =>
729 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
730 RightName =>
731 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
732 ObjectType =>
733 {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''},
734 ObjectId =>
735 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
736 Creator =>
737 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
738 Created =>
739 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
740 LastUpdatedBy =>
741 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
742 LastUpdated =>
743 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
744
745 }
746};
747
748RT::Base->_ImportOverlays();
749
7501;