starting to work...
[freeside.git] / rt / lib / RT / ACL.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 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           => 'MemberId',
187                       OPERATOR        => '=',
188                       VALUE           => $args{'Id'},
189                       ENTRYAGGREGATOR => 'OR'
190                     );
191     } else {
192         if ( defined $args{'Type'} ) {
193             $self->Limit( FIELD           => 'PrincipalType',
194                           OPERATOR        => '=',
195                           VALUE           => $args{'Type'},
196                           ENTRYAGGREGATOR => 'OR'
197                         );
198         }
199
200         # if the principal id points to a user, we really want to point
201         # to their ACL equivalence group. The machinations we're going through
202         # lead me to start to suspect that we really want users and groups
203         # to just be the same table. or _maybe_ that we want an object db.
204         my $princ = RT::Principal->new( RT->SystemUser );
205         $princ->Load( $args{'Id'} );
206         if ( $princ->PrincipalType eq 'User' ) {
207             my $group = RT::Group->new( RT->SystemUser );
208             $group->LoadACLEquivalenceGroup($princ);
209             $args{'Id'} = $group->PrincipalId;
210         }
211         $self->Limit( FIELD           => 'PrincipalId',
212                       OPERATOR        => '=',
213                       VALUE           => $args{'Id'},
214                       ENTRYAGGREGATOR => 'OR'
215                     );
216     }
217 }
218
219
220
221
222 sub Next {
223     my $self = shift;
224
225     my $ACE = $self->SUPER::Next();
226     # Short-circuit having to load up the ->Object
227     return $ACE
228         if $self->CurrentUser->PrincipalObj->Id == RT->SystemUser->Id;
229     if ( ( defined($ACE) ) and ( ref($ACE) ) ) {
230
231         if ( $self->CurrentUser->HasRight( Right  => 'ShowACL',
232                                            Object => $ACE->Object )
233              or $self->CurrentUser->HasRight( Right  => 'ModifyACL',
234                                               Object => $ACE->Object )
235           ) {
236             return ($ACE);
237         }
238
239         #If the user doesn't have the right to show this ACE
240         else {
241             return ( $self->Next() );
242         }
243     }
244
245     #if there never was any ACE
246     else {
247         return (undef);
248     }
249
250 }
251
252
253
254
255 #wrap around _DoSearch  so that we can build the hash of returned
256 #values 
257 sub _DoSearch {
258     my $self = shift;
259    # $RT::Logger->debug("Now in ".$self."->_DoSearch");
260     my $return = $self->SUPER::_DoSearch(@_);
261   #  $RT::Logger->debug("In $self ->_DoSearch. return from SUPER::_DoSearch was $return");
262     if ( $self->{'must_redo_search'} ) {
263         $RT::Logger->crit(
264 "_DoSearch is not so successful as it still needs redo search, won't call _BuildHash"
265         );
266     }
267     else {
268         $self->_BuildHash();
269     }
270     return ($return);
271 }
272
273
274 #Build a hash of this ACL's entries.
275 sub _BuildHash {
276     my $self = shift;
277
278     while (my $entry = $self->Next) {
279         my $hashkey = join '-', map $entry->__Value( $_ ),
280             qw(ObjectType ObjectId RightName PrincipalId PrincipalType);
281
282         $self->{'as_hash'}->{"$hashkey"} =1;
283
284     }
285 }
286
287
288
289 =head2 HasEntry
290
291 =cut
292
293 sub HasEntry {
294
295     my $self = shift;
296     my %args = ( RightScope => undef,
297                  RightAppliesTo => undef,
298                  RightName => undef,
299                  PrincipalId => undef,
300                  PrincipalType => undef,
301                  @_ );
302
303     #if we haven't done the search yet, do it now.
304     $self->_DoSearch();
305
306     if ($self->{'as_hash'}->{ $args{'RightScope'} . "-" .
307                               $args{'RightAppliesTo'} . "-" . 
308                               $args{'RightName'} . "-" .
309                               $args{'PrincipalId'} . "-" .
310                               $args{'PrincipalType'}
311                             } == 1) {
312         return(1);
313     }
314     else {
315         return(undef);
316     }
317 }
318
319 # }}}
320
321
322 =head2 NewItem
323
324 Returns an empty new RT::ACE item
325
326 =cut
327
328 sub NewItem {
329     my $self = shift;
330     return(RT::ACE->new($self->CurrentUser));
331 }
332 RT::Base->_ImportOverlays();
333
334 1;