import torrus 1.0.9
[freeside.git] / rt / lib / RT / CachedGroupMember_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 #                                          <jesse@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 package RT::CachedGroupMember;
50
51 use strict;
52 no warnings qw(redefine);
53
54 =head1 NAME
55
56   RT::CachedGroupMember
57
58 =head1 SYNOPSIS
59
60   use RT::CachedGroupMember;
61
62 =head1 DESCRIPTION
63
64 =head1 METHODS
65
66 =cut
67
68 # {{ Create
69
70 =head2 Create PARAMHASH
71
72 Create takes a hash of values and creates a row in the database:
73
74   'Group' is the "top level" group we're building the cache for. This 
75   is an RT::Principal object
76
77   'Member' is the RT::Principal  of the user or group we're adding to 
78   the cache.
79
80   'ImmediateParent' is the RT::Principal of the group that this 
81   principal belongs to to get here
82
83   int(11) 'Via' is an internal reference to CachedGroupMembers->Id of
84   the "parent" record of this cached group member. It should be empty if 
85   this member is a "direct" member of this group. (In that case, it will 
86   be set to this cached group member's id after creation)
87
88   This routine should _only_ be called by GroupMember->Create
89
90 =cut
91
92 sub Create {
93     my $self = shift;
94     my %args = ( Group           => '',
95                  Member          => '',
96                  ImmediateParent => '',
97                  Via             => '0',
98                  Disabled        => '0',
99                  @_ );
100
101     unless (    $args{'Member'}
102              && UNIVERSAL::isa( $args{'Member'}, 'RT::Principal' )
103              && $args{'Member'}->Id ) {
104         $RT::Logger->debug("$self->Create: bogus Member argument");
105     }
106
107     unless (    $args{'Group'}
108              && UNIVERSAL::isa( $args{'Group'}, 'RT::Principal' )
109              && $args{'Group'}->Id ) {
110         $RT::Logger->debug("$self->Create: bogus Group argument");
111     }
112
113     unless (    $args{'ImmediateParent'}
114              && UNIVERSAL::isa( $args{'ImmediateParent'}, 'RT::Principal' )
115              && $args{'ImmediateParent'}->Id ) {
116         $RT::Logger->debug("$self->Create: bogus ImmediateParent argument");
117     }
118
119     # If the parent group for this group member is disabled, it's disabled too, along with all its children
120     if ( $args{'ImmediateParent'}->Disabled ) {
121         $args{'Disabled'} = $args{'ImmediateParent'}->Disabled;
122     }
123
124     my $id = $self->SUPER::Create(
125                               GroupId           => $args{'Group'}->Id,
126                               MemberId          => $args{'Member'}->Id,
127                               ImmediateParentId => $args{'ImmediateParent'}->Id,
128                               Disabled          => $args{'Disabled'},
129                               Via               => $args{'Via'}, );
130
131     unless ($id) {
132         $RT::Logger->warning( "Couldn't create "
133                            . $args{'Member'}
134                            . " as a cached member of "
135                            . $args{'Group'}->Id . " via "
136                            . $args{'Via'} );
137         return (undef);  #this will percolate up and bail out of the transaction
138     }
139     if ( $self->__Value('Via') == 0 ) {
140         my ( $vid, $vmsg ) = $self->__Set( Field => 'Via', Value => $id );
141         unless ($vid) {
142             $RT::Logger->warning( "Due to a via error, couldn't create "
143                                . $args{'Member'}
144                                . " as a cached member of "
145                                . $args{'Group'}->Id . " via "
146                                . $args{'Via'} );
147             return (undef)
148               ;          #this will percolate up and bail out of the transaction
149         }
150     }
151
152     return $id if $args{'Member'}->id == $args{'Group'}->id;
153
154     if ( $args{'Member'}->IsGroup() ) {
155         my $GroupMembers = $args{'Member'}->Object->MembersObj();
156         while ( my $member = $GroupMembers->Next() ) {
157             my $cached_member =
158               RT::CachedGroupMember->new( $self->CurrentUser );
159             my $c_id = $cached_member->Create(
160                                              Group  => $args{'Group'},
161                                              Member => $member->MemberObj,
162                                              ImmediateParent => $args{'Member'},
163                                              Disabled => $args{'Disabled'},
164                                              Via      => $id );
165             unless ($c_id) {
166                 return (undef);    #percolate the error upwards.
167                      # the caller will log an error and abort the transaction
168             }
169
170         }
171     }
172
173     return ($id);
174
175 }
176
177 # }}}
178
179 # {{{ Delete
180
181 =head2 Delete
182
183 Deletes the current CachedGroupMember from the group it's in and cascades 
184 the delete to all submembers. This routine could be completely excised if
185 mysql supported foreign keys with cascading deletes.
186
187 =cut 
188
189 sub Delete {
190     my $self = shift;
191
192     
193     my $member = $self->MemberObj();
194     if ( $member->IsGroup ) {
195         my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
196
197         $deletable->Limit( FIELD    => 'id',
198                            OPERATOR => '!=',
199                            VALUE    => $self->id );
200         $deletable->Limit( FIELD    => 'Via',
201                            OPERATOR => '=',
202                            VALUE    => $self->id );
203
204         while ( my $kid = $deletable->Next ) {
205             my $kid_err = $kid->Delete();
206             unless ($kid_err) {
207                 $RT::Logger->error(
208                               "Couldn't delete CachedGroupMember " . $kid->Id );
209                 return (undef);
210             }
211         }
212     }
213     my $err = $self->SUPER::Delete();
214     unless ($err) {
215         $RT::Logger->error( "Couldn't delete CachedGroupMember " . $self->Id );
216         return (undef);
217     }
218
219     # Unless $self->GroupObj still has the member recursively $self->MemberObj
220     # (Since we deleted the database row above, $self no longer counts)
221     unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberId ) ) {
222
223
224         #   Find all ACEs granted to $self->GroupId
225         my $acl = RT::ACL->new($RT::SystemUser);
226         $acl->LimitToPrincipal( Id => $self->GroupId );
227
228
229         while ( my $this_ace = $acl->Next() ) {
230             #       Find all ACEs which $self-MemberObj has delegated from $this_ace
231             my $delegations = RT::ACL->new($RT::SystemUser);
232             $delegations->DelegatedFrom( Id => $this_ace->Id );
233             $delegations->DelegatedBy( Id => $self->MemberId );
234
235             # For each delegation 
236             while ( my $delegation = $delegations->Next ) {
237                 # WHACK IT
238                 my $del_ret = $delegation->_Delete(InsideTransaction => 1);
239                 unless ($del_ret) {
240                     $RT::Logger->crit("Couldn't delete an ACL delegation that we know exists ". $delegation->Id);
241                     return(undef);
242                 }
243             }
244         }
245     }
246     return ($err);
247 }
248
249 # }}}
250
251 # {{{ SetDisabled
252
253 =head2 SetDisabled
254
255 SetDisableds the current CachedGroupMember from the group it's in and cascades 
256 the SetDisabled to all submembers. This routine could be completely excised if
257 mysql supported foreign keys with cascading SetDisableds.
258
259 =cut 
260
261 sub SetDisabled {
262     my $self = shift;
263     my $val = shift;
264  
265     # if it's already disabled, we're good.
266     return (1) if ( $self->__Value('Disabled') == $val);
267     my $err = $self->SUPER::SetDisabled($val);
268     my ($retval, $msg) = $err->as_array();
269     unless ($retval) {
270         $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $self->Id .": $msg");
271         return ($err);
272     }
273     
274     my $member = $self->MemberObj();
275     if ( $member->IsGroup ) {
276         my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
277
278         $deletable->Limit( FIELD    => 'Via', OPERATOR => '=', VALUE    => $self->id );
279         $deletable->Limit( FIELD    => 'id', OPERATOR => '!=', VALUE    => $self->id );
280
281         while ( my $kid = $deletable->Next ) {
282             my $kid_err = $kid->SetDisabled($val );
283             unless ($kid_err) {
284                 $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $kid->Id );
285                 return ($kid_err);
286             }
287         }
288     }
289
290     # Unless $self->GroupObj still has the member recursively $self->MemberObj
291     # (Since we SetDisabledd the database row above, $self no longer counts)
292     unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberId ) ) {
293         #   Find all ACEs granted to $self->GroupId
294         my $acl = RT::ACL->new($RT::SystemUser);
295         $acl->LimitToPrincipal( Id => $self->GroupId );
296
297         while ( my $this_ace = $acl->Next() ) {
298             #       Find all ACEs which $self-MemberObj has delegated from $this_ace
299             my $delegations = RT::ACL->new($RT::SystemUser);
300             $delegations->DelegatedFrom( Id => $this_ace->Id );
301             $delegations->DelegatedBy( Id => $self->MemberId );
302
303             # For each delegation,  blow away the delegation
304             while ( my $delegation = $delegations->Next ) {
305                 # WHACK IT
306                 my $del_ret = $delegation->_Delete(InsideTransaction => 1);
307                 unless ($del_ret) {
308                     $RT::Logger->crit("Couldn't delete an ACL delegation that we know exists ". $delegation->Id);
309                     return(undef);
310                 }
311             }
312         }
313     }
314     return ($err);
315 }
316
317 # }}}
318
319 # {{{ GroupObj
320
321 =head2 GroupObj  
322
323 Returns the RT::Principal object for this group Group
324
325 =cut
326
327 sub GroupObj {
328     my $self      = shift;
329     my $principal = RT::Principal->new( $self->CurrentUser );
330     $principal->Load( $self->GroupId );
331     return ($principal);
332 }
333
334 # }}}
335
336 # {{{ ImmediateParentObj
337
338 =head2 ImmediateParentObj  
339
340 Returns the RT::Principal object for this group ImmediateParent
341
342 =cut
343
344 sub ImmediateParentObj {
345     my $self      = shift;
346     my $principal = RT::Principal->new( $self->CurrentUser );
347     $principal->Load( $self->ImmediateParentId );
348     return ($principal);
349 }
350
351 # }}}
352
353 # {{{ MemberObj
354
355 =head2 MemberObj  
356
357 Returns the RT::Principal object for this group member
358
359 =cut
360
361 sub MemberObj {
362     my $self      = shift;
363     my $principal = RT::Principal->new( $self->CurrentUser );
364     $principal->Load( $self->MemberId );
365     return ($principal);
366 }
367
368 # }}}
369 1;