Upgrade to 4.2.2
[usit-rt.git] / lib / RT / GroupMember.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
320f0092 5# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
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::GroupMember - a member of an RT Group
52
53=head1 SYNOPSIS
54
55RT::GroupMember should never be called directly. It should ONLY
56only be accessed through the helper functions in RT::Group;
57
58If you're operating on an RT::GroupMember object yourself, you B<ARE>
59doing something wrong.
60
61=head1 DESCRIPTION
62
63
64
65
66=head1 METHODS
67
68
69
70
71=cut
72
73
74package RT::GroupMember;
75
76use strict;
77use warnings;
78
79
80use base 'RT::Record';
81
82sub Table {'GroupMembers'}
83
84
85use RT::CachedGroupMembers;
86
87
88=head2 Create { Group => undef, Member => undef }
89
90Add a Principal to the group Group.
91if the Principal is a group, automatically inserts all
92members of the principal into the cached members table recursively down.
93
94Both Group and Member are expected to be RT::Principal objects
95
96=cut
97
af59614d
MKG
98sub _InsertCGM {
99 my $self = shift;
100
101 my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser );
102 my $cached_id = $cached_member->Create(
103 Member => $self->MemberObj,
104 Group => $self->GroupObj,
105 ImmediateParent => $self->GroupObj,
106 Via => '0'
107 );
108
109
110 #When adding a member to a group, we need to go back
111 #and popuplate the CachedGroupMembers of all the groups that group is part of .
112
113 my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser );
114
115 # find things which have the current group as a member.
116 # $group is an RT::Principal for the group.
117 $cgm->LimitToGroupsWithMember( $self->GroupId );
118 $cgm->Limit(
119 SUBCLAUSE => 'filter', # dont't mess up with prev condition
120 FIELD => 'MemberId',
121 OPERATOR => '!=',
122 VALUE => 'main.GroupId',
123 QUOTEVALUE => 0,
124 ENTRYAGGREGATOR => 'AND',
125 );
126
127 while ( my $parent_member = $cgm->Next ) {
128 my $parent_id = $parent_member->MemberId;
129 my $via = $parent_member->Id;
130 my $group_id = $parent_member->GroupId;
131
132 my $other_cached_member =
133 RT::CachedGroupMember->new( $self->CurrentUser );
134 my $other_cached_id = $other_cached_member->Create(
135 Member => $self->MemberObj,
136 Group => $parent_member->GroupObj,
137 ImmediateParent => $parent_member->MemberObj,
138 Via => $parent_member->Id
139 );
140 unless ($other_cached_id) {
141 $RT::Logger->err( "Couldn't add " . $self->MemberId
142 . " as a submember of a supergroup" );
143 return;
144 }
145 }
146
147 return $cached_id;
148}
149
84fb5b46
MKG
150sub Create {
151 my $self = shift;
152 my %args = (
153 Group => undef,
154 Member => undef,
155 InsideTransaction => undef,
156 @_
157 );
158
159 unless ($args{'Group'} &&
160 UNIVERSAL::isa($args{'Group'}, 'RT::Principal') &&
161 $args{'Group'}->Id ) {
162
163 $RT::Logger->warning("GroupMember::Create called with a bogus Group arg");
164 return (undef);
165 }
166
167 unless($args{'Group'}->IsGroup) {
168 $RT::Logger->warning("Someone tried to add a member to a user instead of a group");
169 return (undef);
170 }
171
172 unless ($args{'Member'} &&
173 UNIVERSAL::isa($args{'Member'}, 'RT::Principal') &&
174 $args{'Member'}->Id) {
175 $RT::Logger->warning("GroupMember::Create called with a bogus Principal arg");
176 return (undef);
177 }
178
179
180 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
181 # TODO what about the groups key cache?
182 RT::Principal->InvalidateACLCache();
183
184 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
185
186 # We really need to make sure we don't add any members to this group
187 # that contain the group itself. that would, um, suck.
188 # (and recurse infinitely) Later, we can add code to check this in the
189 # cache and bail so we can support cycling directed graphs
190
191 if ($args{'Member'}->IsGroup) {
192 my $member_object = $args{'Member'}->Object;
193 if ($member_object->HasMemberRecursively($args{'Group'})) {
194 $RT::Logger->debug("Adding that group would create a loop");
195 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
196 return(undef);
197 }
198 elsif ( $args{'Member'}->Id == $args{'Group'}->Id) {
199 $RT::Logger->debug("Can't add a group to itself");
200 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
201 return(undef);
202 }
203 }
204
205
206 my $id = $self->SUPER::Create(
207 GroupId => $args{'Group'}->Id,
208 MemberId => $args{'Member'}->Id
209 );
210
211 unless ($id) {
212 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
213 return (undef);
214 }
215
af59614d
MKG
216 my $clone = RT::GroupMember->new( $self->CurrentUser );
217 $clone->Load( $id );
218 my $cached_id = $clone->_InsertCGM;
84fb5b46
MKG
219
220 unless ($cached_id) {
221 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
222 return (undef);
223 }
224
225 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
226
227 return ($id);
228}
229
230
231
232=head2 _StashUser PRINCIPAL
233
234Create { Group => undef, Member => undef }
235
236Creates an entry in the groupmembers table, which lists a user
237as a member of himself. This makes ACL checks a whole bunch easier.
238This happens once on user create and never ever gets yanked out.
239
240PRINCIPAL is expected to be an RT::Principal object for a user
241
242This routine expects to be called inside a transaction by RT::User->Create
243
244=cut
245
246sub _StashUser {
247 my $self = shift;
248 my %args = (
249 Group => undef,
250 Member => undef,
251 @_
252 );
253
254 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
255 # TODO what about the groups key cache?
256 RT::Principal->InvalidateACLCache();
257
258
259 # We really need to make sure we don't add any members to this group
260 # that contain the group itself. that would, um, suck.
261 # (and recurse infinitely) Later, we can add code to check this in the
262 # cache and bail so we can support cycling directed graphs
263
264 my $id = $self->SUPER::Create(
265 GroupId => $args{'Group'}->Id,
266 MemberId => $args{'Member'}->Id,
267 );
268
269 unless ($id) {
270 return (undef);
271 }
272
273 my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser );
274 my $cached_id = $cached_member->Create(
275 Member => $args{'Member'},
276 Group => $args{'Group'},
277 ImmediateParent => $args{'Group'},
278 Via => '0'
279 );
280
281 unless ($cached_id) {
282 return (undef);
283 }
284
285 return ($id);
286}
287
288
289
290=head2 Delete
291
292Takes no arguments. deletes the currently loaded member from the
293group in question.
294
295Expects to be called _outside_ a transaction
296
297=cut
298
299sub Delete {
300 my $self = shift;
301
302
303 $RT::Handle->BeginTransaction();
304
305 # Find all occurrences of this member as a member of this group
306 # in the cache and nuke them, recursively.
307
308 # The following code will delete all Cached Group members
309 # where this member's group is _not_ the primary group
310 # (Ie if we're deleting C as a member of B, and B happens to be
311 # a member of A, will delete C as a member of A without touching
312 # C as a member of B
313
314 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
315
316 $cached_submembers->Limit(
317 FIELD => 'MemberId',
318 OPERATOR => '=',
319 VALUE => $self->MemberObj->Id
320 );
321
322 $cached_submembers->Limit(
323 FIELD => 'ImmediateParentId',
324 OPERATOR => '=',
325 VALUE => $self->GroupObj->Id
326 );
327
328
329
330
331
332 while ( my $item_to_del = $cached_submembers->Next() ) {
333 my $del_err = $item_to_del->Delete();
334 unless ($del_err) {
335 $RT::Handle->Rollback();
336 $RT::Logger->warning("Couldn't delete cached group submember ".$item_to_del->Id);
337 return (undef);
338 }
339 }
340
341 my ($err, $msg) = $self->SUPER::Delete();
342 unless ($err) {
343 $RT::Logger->warning("Couldn't delete cached group submember ".$self->Id);
344 $RT::Handle->Rollback();
345 return (undef);
346 }
347
348 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
349 # TODO what about the groups key cache?
350 RT::Principal->InvalidateACLCache();
351
352 $RT::Handle->Commit();
353 return ($err);
354
355}
356
357
358
359=head2 MemberObj
360
361Returns an RT::Principal object for the Principal specified by $self->MemberId
362
363=cut
364
365sub MemberObj {
366 my $self = shift;
367 unless ( defined( $self->{'Member_obj'} ) ) {
368 $self->{'Member_obj'} = RT::Principal->new( $self->CurrentUser );
369 $self->{'Member_obj'}->Load( $self->MemberId ) if ($self->MemberId);
370 }
371 return ( $self->{'Member_obj'} );
372}
373
374
375
376=head2 GroupObj
377
378Returns an RT::Principal object for the Group specified in $self->GroupId
379
380=cut
381
382sub GroupObj {
383 my $self = shift;
384 unless ( defined( $self->{'Group_obj'} ) ) {
385 $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser );
386 $self->{'Group_obj'}->Load( $self->GroupId );
387 }
388 return ( $self->{'Group_obj'} );
389}
390
391
392
393
394
395
396=head2 id
397
398Returns the current value of id.
399(In the database, id is stored as int(11).)
400
401
402=cut
403
404
405=head2 GroupId
406
407Returns the current value of GroupId.
408(In the database, GroupId is stored as int(11).)
409
410
411
412=head2 SetGroupId VALUE
413
414
415Set GroupId to VALUE.
416Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
417(In the database, GroupId will be stored as a int(11).)
418
419
420=cut
421
422
423=head2 MemberId
424
425Returns the current value of MemberId.
426(In the database, MemberId is stored as int(11).)
427
428
429
430=head2 SetMemberId VALUE
431
432
433Set MemberId to VALUE.
434Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
435(In the database, MemberId will be stored as a int(11).)
436
437
438=cut
439
440
441=head2 Creator
442
443Returns the current value of Creator.
444(In the database, Creator is stored as int(11).)
445
446
447=cut
448
449
450=head2 Created
451
452Returns the current value of Created.
453(In the database, Created is stored as datetime.)
454
455
456=cut
457
458
459=head2 LastUpdatedBy
460
461Returns the current value of LastUpdatedBy.
462(In the database, LastUpdatedBy is stored as int(11).)
463
464
465=cut
466
467
468=head2 LastUpdated
469
470Returns the current value of LastUpdated.
471(In the database, LastUpdated is stored as datetime.)
472
473
474=cut
475
476
477
478sub _CoreAccessible {
479 {
480
481 id =>
af59614d 482 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 483 GroupId =>
af59614d 484 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 485 MemberId =>
af59614d 486 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 487 Creator =>
af59614d 488 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 489 Created =>
af59614d 490 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46 491 LastUpdatedBy =>
af59614d 492 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 493 LastUpdated =>
af59614d 494 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46
MKG
495
496 }
497};
498
af59614d
MKG
499sub FindDependencies {
500 my $self = shift;
501 my ($walker, $deps) = @_;
502
503 $self->SUPER::FindDependencies($walker, $deps);
504
505 $deps->Add( out => $self->GroupObj->Object );
506 $deps->Add( out => $self->MemberObj->Object );
507}
508
509sub PreInflate {
510 my $class = shift;
511 my ($importer, $uid, $data) = @_;
512
513 $class->SUPER::PreInflate( $importer, $uid, $data );
514
515 my $obj = RT::GroupMember->new( RT->SystemUser );
516 $obj->LoadByCols(
517 GroupId => $data->{GroupId},
518 MemberId => $data->{MemberId},
519 );
520 if ($obj->id) {
521 $importer->Resolve( $uid => ref($obj) => $obj->Id );
522 return;
523 }
524
525 return 1;
526}
527
528sub PostInflate {
529 my $self = shift;
530
531 $self->_InsertCGM;
532}
533
84fb5b46
MKG
534RT::Base->_ImportOverlays();
535
5361;