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