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