821b619bc42ceee4df77a6fadc0eb869a21644f3
[freeside.git] / rt / lib / RT / CachedGroupMember.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2018 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 package RT::CachedGroupMember;
50
51 use strict;
52 use warnings;
53
54
55 use base 'RT::Record';
56
57 sub Table {'CachedGroupMembers'}
58
59 =head1 NAME
60
61   RT::CachedGroupMember
62
63 =head1 SYNOPSIS
64
65   use RT::CachedGroupMember;
66
67 =head1 DESCRIPTION
68
69 =head1 METHODS
70
71 =cut
72
73 # {{ Create
74
75 =head2 Create PARAMHASH
76
77 Create takes a hash of values and creates a row in the database:
78
79   'Group' is the "top level" group we're building the cache for. This 
80   is an RT::Principal object
81
82   'Member' is the RT::Principal  of the user or group we're adding to 
83   the cache.
84
85   'ImmediateParent' is the RT::Principal of the group that this 
86   principal belongs to to get here
87
88   int(11) 'Via' is an internal reference to CachedGroupMembers->Id of
89   the "parent" record of this cached group member. It should be empty if 
90   this member is a "direct" member of this group. (In that case, it will 
91   be set to this cached group member's id after creation)
92
93   This routine should _only_ be called by GroupMember->Create
94
95 =cut
96
97 sub Create {
98     my $self = shift;
99     my %args = ( Group           => '',
100                  Member          => '',
101                  ImmediateParent => '',
102                  Via             => '0',
103                  Disabled        => '0',
104                  @_ );
105
106     unless (    $args{'Member'}
107              && UNIVERSAL::isa( $args{'Member'}, 'RT::Principal' )
108              && $args{'Member'}->Id ) {
109         $RT::Logger->debug("$self->Create: bogus Member argument");
110     }
111
112     unless (    $args{'Group'}
113              && UNIVERSAL::isa( $args{'Group'}, 'RT::Principal' )
114              && $args{'Group'}->Id ) {
115         $RT::Logger->debug("$self->Create: bogus Group argument");
116     }
117
118     unless (    $args{'ImmediateParent'}
119              && UNIVERSAL::isa( $args{'ImmediateParent'}, 'RT::Principal' )
120              && $args{'ImmediateParent'}->Id ) {
121         $RT::Logger->debug("$self->Create: bogus ImmediateParent argument");
122     }
123
124     # If the parent group for this group member is disabled, it's disabled too, along with all its children
125     if ( $args{'ImmediateParent'}->Disabled ) {
126         $args{'Disabled'} = $args{'ImmediateParent'}->Disabled;
127     }
128
129     my $id = $self->SUPER::Create(
130                               GroupId           => $args{'Group'}->Id,
131                               MemberId          => $args{'Member'}->Id,
132                               ImmediateParentId => $args{'ImmediateParent'}->Id,
133                               Disabled          => $args{'Disabled'},
134                               Via               => $args{'Via'}, );
135
136     unless ($id) {
137         $RT::Logger->warning( "Couldn't create "
138                            . $args{'Member'}
139                            . " as a cached member of "
140                            . $args{'Group'}->Id . " via "
141                            . $args{'Via'} );
142         return (undef);  #this will percolate up and bail out of the transaction
143     }
144     if ( $self->__Value('Via') == 0 ) {
145         my ( $vid, $vmsg ) = $self->__Set( Field => 'Via', Value => $id );
146         unless ($vid) {
147             $RT::Logger->warning( "Due to a via error, couldn't create "
148                                . $args{'Member'}
149                                . " as a cached member of "
150                                . $args{'Group'}->Id . " via "
151                                . $args{'Via'} );
152             return (undef)
153               ;          #this will percolate up and bail out of the transaction
154         }
155     }
156
157     return $id if $args{'Member'}->id == $args{'Group'}->id;
158
159     if ( $args{'Member'}->IsGroup() ) {
160         my $GroupMembers = $args{'Member'}->Object->MembersObj();
161         while ( my $member = $GroupMembers->Next() ) {
162             my $cached_member =
163               RT::CachedGroupMember->new( $self->CurrentUser );
164             my $c_id = $cached_member->Create(
165                                              Group  => $args{'Group'},
166                                              Member => $member->MemberObj,
167                                              ImmediateParent => $args{'Member'},
168                                              Disabled => $args{'Disabled'},
169                                              Via      => $id );
170             unless ($c_id) {
171                 return (undef);    #percolate the error upwards.
172                      # the caller will log an error and abort the transaction
173             }
174
175         }
176     }
177
178     return ($id);
179
180 }
181
182
183
184 =head2 Delete
185
186 Deletes the current CachedGroupMember from the group it's in and cascades 
187 the delete to all submembers. This routine could be completely excised if
188 mysql supported foreign keys with cascading deletes.
189
190 =cut 
191
192 sub Delete {
193     my $self = shift;
194
195     
196     my $member = $self->MemberObj();
197     if ( $member->IsGroup ) {
198         my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
199
200         $deletable->Limit( FIELD    => 'id',
201                            OPERATOR => '!=',
202                            VALUE    => $self->id );
203         $deletable->Limit( FIELD    => 'Via',
204                            OPERATOR => '=',
205                            VALUE    => $self->id );
206
207         while ( my $kid = $deletable->Next ) {
208             my $kid_err = $kid->Delete();
209             unless ($kid_err) {
210                 $RT::Logger->error(
211                               "Couldn't delete CachedGroupMember " . $kid->Id );
212                 return (undef);
213             }
214         }
215     }
216     my $ret = $self->SUPER::Delete();
217     unless ($ret) {
218         $RT::Logger->error( "Couldn't delete CachedGroupMember " . $self->Id );
219         return (undef);
220     }
221     return $ret;
222 }
223
224
225
226 =head2 SetDisabled
227
228 SetDisableds the current CachedGroupMember from the group it's in and cascades 
229 the SetDisabled to all submembers. This routine could be completely excised if
230 mysql supported foreign keys with cascading SetDisableds.
231
232 =cut 
233
234 sub SetDisabled {
235     my $self = shift;
236     my $val = shift;
237  
238     # if it's already disabled, we're good.
239     return (1) if ( $self->__Value('Disabled') == $val);
240     my $err = $self->_Set(Field => 'Disabled', Value => $val);
241     my ($retval, $msg) = $err->as_array();
242     unless ($retval) {
243         $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $self->Id .": $msg");
244         return ($err);
245     }
246     
247     my $member = $self->MemberObj();
248     if ( $member->IsGroup ) {
249         my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
250
251         $deletable->Limit( FIELD    => 'Via', OPERATOR => '=', VALUE    => $self->id );
252         $deletable->Limit( FIELD    => 'id', OPERATOR => '!=', VALUE    => $self->id );
253
254         while ( my $kid = $deletable->Next ) {
255             my $kid_err = $kid->SetDisabled($val );
256             unless ($kid_err) {
257                 $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $kid->Id );
258                 return ($kid_err);
259             }
260         }
261     }
262     return ($err);
263 }
264
265
266
267 =head2 GroupObj  
268
269 Returns the RT::Principal object for this group Group
270
271 =cut
272
273 sub GroupObj {
274     my $self      = shift;
275     my $principal = RT::Principal->new( $self->CurrentUser );
276     $principal->Load( $self->GroupId );
277     return ($principal);
278 }
279
280
281
282 =head2 ImmediateParentObj  
283
284 Returns the RT::Principal object for this group ImmediateParent
285
286 =cut
287
288 sub ImmediateParentObj {
289     my $self      = shift;
290     my $principal = RT::Principal->new( $self->CurrentUser );
291     $principal->Load( $self->ImmediateParentId );
292     return ($principal);
293 }
294
295
296
297 =head2 MemberObj  
298
299 Returns the RT::Principal object for this group member
300
301 =cut
302
303 sub MemberObj {
304     my $self      = shift;
305     my $principal = RT::Principal->new( $self->CurrentUser );
306     $principal->Load( $self->MemberId );
307     return ($principal);
308 }
309
310 # }}}
311
312
313
314
315
316
317 =head2 id
318
319 Returns the current value of id.
320 (In the database, id is stored as int(11).)
321
322
323 =cut
324
325
326 =head2 GroupId
327
328 Returns the current value of GroupId.
329 (In the database, GroupId is stored as int(11).)
330
331
332
333 =head2 SetGroupId VALUE
334
335
336 Set GroupId to VALUE.
337 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
338 (In the database, GroupId will be stored as a int(11).)
339
340
341 =cut
342
343
344 =head2 MemberId
345
346 Returns the current value of MemberId.
347 (In the database, MemberId is stored as int(11).)
348
349
350
351 =head2 SetMemberId VALUE
352
353
354 Set MemberId to VALUE.
355 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
356 (In the database, MemberId will be stored as a int(11).)
357
358
359 =cut
360
361
362 =head2 Via
363
364 Returns the current value of Via.
365 (In the database, Via is stored as int(11).)
366
367
368
369 =head2 SetVia VALUE
370
371
372 Set Via to VALUE.
373 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
374 (In the database, Via will be stored as a int(11).)
375
376
377 =cut
378
379
380 =head2 ImmediateParentId
381
382 Returns the current value of ImmediateParentId.
383 (In the database, ImmediateParentId is stored as int(11).)
384
385
386
387 =head2 SetImmediateParentId VALUE
388
389
390 Set ImmediateParentId to VALUE.
391 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
392 (In the database, ImmediateParentId will be stored as a int(11).)
393
394
395 =cut
396
397
398 =head2 Disabled
399
400 Returns the current value of Disabled.
401 (In the database, Disabled is stored as smallint(6).)
402
403
404
405 =head2 SetDisabled VALUE
406
407
408 Set Disabled to VALUE.
409 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
410 (In the database, Disabled will be stored as a smallint(6).)
411
412
413 =cut
414
415
416
417 sub _CoreAccessible {
418     {
419
420         id =>
421                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
422         GroupId =>
423                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
424         MemberId =>
425                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
426         Via =>
427                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
428         ImmediateParentId =>
429                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
430         Disabled =>
431                 {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
432
433  }
434 };
435
436 sub Serialize {
437     die "CachedGroupMembers should never be serialized";
438 }
439
440 sub __DependsOn
441 {
442     my $self = shift;
443     my %args = (
444         Shredder => undef,
445         Dependencies => undef,
446         @_,
447     );
448     my $deps = $args{'Dependencies'};
449     my $list = [];
450
451 # deep memebership
452     my $objs = RT::CachedGroupMembers->new( $self->CurrentUser );
453     $objs->Limit( FIELD => 'Via', VALUE => $self->Id );
454     $objs->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id );
455     push( @$list, $objs );
456
457 # principal lost group membership and lost some rights which he could delegate to
458 # some body
459
460 # XXX: Here is problem cause HasMemberRecursively would return true allways
461 # cause we didn't delete anything yet. :(
462     # if pricipal is not member anymore(could be via other groups) then proceed
463     if( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) {
464         my $acl = RT::ACL->new( $self->CurrentUser );
465         $acl->LimitToPrincipal( Id => $self->GroupId );
466     }
467
468
469     $deps->_PushDependencies(
470         BaseObject => $self,
471         Flags => RT::Shredder::Constants::DEPENDS_ON,
472         TargetObjects => $list,
473         Shredder => $args{'Shredder'}
474     );
475
476     return $self->SUPER::__DependsOn( %args );
477 }
478
479 RT::Base->_ImportOverlays();
480
481 1;