0662b3955b0c55822902f9b2b6a59ecae96cebf5
[freeside.git] / rt / lib / RT / ACL.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 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 NAME
50
51   RT::ACL - collection of RT ACE objects
52
53 =head1 SYNOPSIS
54
55   use RT::ACL;
56 my $ACL = RT::ACL->new($CurrentUser);
57
58 =head1 DESCRIPTION
59
60
61 =head1 METHODS
62
63
64 =cut
65
66
67 package RT::ACL;
68 use RT::ACE;
69
70 use base 'RT::SearchBuilder';
71
72 sub Table { 'ACL'}
73
74 use strict;
75 use warnings;
76
77
78
79 =head2 Next
80
81 Hand out the next ACE that was found
82
83 =cut
84
85
86
87 =head2 LimitToObject $object
88
89 Limit the ACL to rights for the object $object. It needs to be an RT::Record class.
90
91 =cut
92
93 sub LimitToObject {
94     my $self = shift;
95     my $obj  = shift;
96
97     my $obj_type = ref($obj)||$obj;
98     my $obj_id = eval { $obj->id};
99
100     my $object_clause = 'possible_objects';
101     $self->_OpenParen($object_clause);
102     $self->Limit(
103         SUBCLAUSE       => $object_clause,
104         FIELD           => 'ObjectType',
105         OPERATOR        => '=',
106         VALUE           => (ref($obj)||$obj),
107         ENTRYAGGREGATOR => 'OR' # That "OR" applies to the separate objects we're searching on, not "Type Or ID"
108     );
109     if ($obj_id) {
110     $self->Limit(
111         SUBCLAUSE       => $object_clause,
112         FIELD           => 'ObjectId',
113         OPERATOR        => '=',
114         VALUE           => $obj_id,
115         ENTRYAGGREGATOR => 'AND',
116         QUOTEVALUE      => 0
117     );
118     }
119     $self->_CloseParen($object_clause);
120
121 }
122
123
124
125 =head2 LimitNotObject $object
126
127 Limit the ACL to rights NOT on the object $object.  $object needs to be
128 an RT::Record class.
129
130 =cut
131
132 sub LimitNotObject {
133     my $self = shift;
134     my $obj  = shift;
135     unless ( defined($obj)
136         && ref($obj)
137         && UNIVERSAL::can( $obj, 'id' )
138         && $obj->id )
139     {
140         return undef;
141     }
142     $self->Limit( FIELD => 'ObjectType',
143                   OPERATOR => '!=',
144                   VALUE => ref($obj),
145                   ENTRYAGGREGATOR => 'OR',
146                   SUBCLAUSE => $obj->id
147                 );
148     $self->Limit( FIELD => 'ObjectId',
149                   OPERATOR => '!=',
150                   VALUE => $obj->id,
151                   ENTRYAGGREGATOR => 'OR',
152                   QUOTEVALUE => 0,
153                   SUBCLAUSE => $obj->id
154                 );
155 }
156
157
158
159 =head2 LimitToPrincipal { Type => undef, Id => undef, IncludeGroupMembership => undef }
160
161 Limit the ACL to the principal with PrincipalId Id and PrincipalType Type
162
163 Id is not optional.
164 Type is.
165
166 if IncludeGroupMembership => 1 is specified, ACEs which apply to the principal due to group membership will be included in the resultset.
167
168
169 =cut
170
171 sub LimitToPrincipal {
172     my $self = shift;
173     my %args = ( Type                   => undef,
174                  Id                     => undef,
175                  IncludeGroupMembership => undef,
176                  @_
177                );
178     if ( $args{'IncludeGroupMembership'} ) {
179         my $cgm = $self->NewAlias('CachedGroupMembers');
180         $self->Join( ALIAS1 => 'main',
181                      FIELD1 => 'PrincipalId',
182                      ALIAS2 => $cgm,
183                      FIELD2 => 'GroupId'
184                    );
185         $self->Limit( ALIAS => $cgm,
186                       FIELD => 'Disabled',
187                       VALUE => 0 );
188         $self->Limit( ALIAS           => $cgm,
189                       FIELD           => 'MemberId',
190                       OPERATOR        => '=',
191                       VALUE           => $args{'Id'},
192                       ENTRYAGGREGATOR => 'OR'
193                     );
194     } else {
195         if ( defined $args{'Type'} ) {
196             $self->Limit( FIELD           => 'PrincipalType',
197                           OPERATOR        => '=',
198                           VALUE           => $args{'Type'},
199                           ENTRYAGGREGATOR => 'OR'
200                         );
201         }
202
203         # if the principal id points to a user, we really want to point
204         # to their ACL equivalence group. The machinations we're going through
205         # lead me to start to suspect that we really want users and groups
206         # to just be the same table. or _maybe_ that we want an object db.
207         my $princ = RT::Principal->new( RT->SystemUser );
208         $princ->Load( $args{'Id'} );
209         if ( $princ->PrincipalType eq 'User' ) {
210             my $group = RT::Group->new( RT->SystemUser );
211             $group->LoadACLEquivalenceGroup($princ);
212             $args{'Id'} = $group->PrincipalId;
213         }
214         $self->Limit( FIELD           => 'PrincipalId',
215                       OPERATOR        => '=',
216                       VALUE           => $args{'Id'},
217                       ENTRYAGGREGATOR => 'OR'
218                     );
219     }
220 }
221
222
223
224
225 sub Next {
226     my $self = shift;
227
228     my $ACE = $self->SUPER::Next();
229     # Short-circuit having to load up the ->Object
230     return $ACE
231         if $self->CurrentUser->PrincipalObj->Id == RT->SystemUser->Id;
232     if ( ( defined($ACE) ) and ( ref($ACE) ) ) {
233
234         if ( $self->CurrentUser->HasRight( Right  => 'ShowACL',
235                                            Object => $ACE->Object )
236              or $self->CurrentUser->HasRight( Right  => 'ModifyACL',
237                                               Object => $ACE->Object )
238           ) {
239             return ($ACE);
240         }
241
242         #If the user doesn't have the right to show this ACE
243         else {
244             return ( $self->Next() );
245         }
246     }
247
248     #if there never was any ACE
249     else {
250         return (undef);
251     }
252
253 }
254
255
256
257
258 #wrap around _DoSearch  so that we can build the hash of returned
259 #values 
260 sub _DoSearch {
261     my $self = shift;
262    # $RT::Logger->debug("Now in ".$self."->_DoSearch");
263     my $return = $self->SUPER::_DoSearch(@_);
264   #  $RT::Logger->debug("In $self ->_DoSearch. return from SUPER::_DoSearch was $return");
265     if ( $self->{'must_redo_search'} ) {
266         $RT::Logger->crit(
267 "_DoSearch is not so successful as it still needs redo search, won't call _BuildHash"
268         );
269     }
270     else {
271         $self->_BuildHash();
272     }
273     return ($return);
274 }
275
276
277 #Build a hash of this ACL's entries.
278 sub _BuildHash {
279     my $self = shift;
280
281     while (my $entry = $self->Next) {
282         my $hashkey = join '-', map $entry->__Value( $_ ),
283             qw(ObjectType ObjectId RightName PrincipalId PrincipalType);
284
285         $self->{'as_hash'}->{"$hashkey"} =1;
286
287     }
288 }
289
290
291
292 =head2 HasEntry
293
294 =cut
295
296 sub HasEntry {
297
298     my $self = shift;
299     my %args = ( RightScope => undef,
300                  RightAppliesTo => undef,
301                  RightName => undef,
302                  PrincipalId => undef,
303                  PrincipalType => undef,
304                  @_ );
305
306     #if we haven't done the search yet, do it now.
307     $self->_DoSearch();
308
309     if ($self->{'as_hash'}->{ $args{'RightScope'} . "-" .
310                               $args{'RightAppliesTo'} . "-" . 
311                               $args{'RightName'} . "-" .
312                               $args{'PrincipalId'} . "-" .
313                               $args{'PrincipalType'}
314                             } == 1) {
315         return(1);
316     }
317     else {
318         return(undef);
319     }
320 }
321
322 # }}}
323
324
325 =head2 NewItem
326
327 Returns an empty new RT::ACE item
328
329 =cut
330
331 sub NewItem {
332     my $self = shift;
333     return(RT::ACE->new($self->CurrentUser));
334 }
335 RT::Base->_ImportOverlays();
336
337 1;