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