1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
51 RT::GroupMember - a member of an RT Group
55 RT::GroupMember should never be called directly. It should ONLY
56 only be accessed through the helper functions in RT::Group;
58 If you're operating on an RT::GroupMember object yourself, you B<ARE>
59 doing something wrong.
74 package RT::GroupMember;
80 use base 'RT::Record';
82 sub Table {'GroupMembers'}
85 use RT::CachedGroupMembers;
88 =head2 Create { Group => undef, Member => undef }
90 Add a Principal to the group Group.
91 if the Principal is a group, automatically inserts all
92 members of the principal into the cached members table recursively down.
94 Both Group and Member are expected to be RT::Principal objects
103 InsideTransaction => undef,
107 unless ($args{'Group'} &&
108 UNIVERSAL::isa($args{'Group'}, 'RT::Principal') &&
109 $args{'Group'}->Id ) {
111 $RT::Logger->warning("GroupMember::Create called with a bogus Group arg");
115 unless($args{'Group'}->IsGroup) {
116 $RT::Logger->warning("Someone tried to add a member to a user instead of a group");
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");
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();
132 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
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
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'});
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'});
154 my $id = $self->SUPER::Create(
155 GroupId => $args{'Group'}->Id,
156 MemberId => $args{'Member'}->Id
160 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
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'},
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 .
176 my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser );
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 );
182 SUBCLAUSE => 'filter', # dont't mess up with prev condition
185 VALUE => 'main.GroupId',
187 ENTRYAGGREGATOR => 'AND',
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;
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
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'});
211 unless ($cached_id) {
212 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
216 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
223 =head2 _StashUser PRINCIPAL
225 Create { Group => undef, Member => undef }
227 Creates an entry in the groupmembers table, which lists a user
228 as a member of himself. This makes ACL checks a whole bunch easier.
229 This happens once on user create and never ever gets yanked out.
231 PRINCIPAL is expected to be an RT::Principal object for a user
233 This routine expects to be called inside a transaction by RT::User->Create
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();
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
255 my $id = $self->SUPER::Create(
256 GroupId => $args{'Group'}->Id,
257 MemberId => $args{'Member'}->Id,
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'},
272 unless ($cached_id) {
283 Takes no arguments. deletes the currently loaded member from the
286 Expects to be called _outside_ a transaction
294 $RT::Handle->BeginTransaction();
296 # Find all occurrences of this member as a member of this group
297 # in the cache and nuke them, recursively.
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
305 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
307 $cached_submembers->Limit(
310 VALUE => $self->MemberObj->Id
313 $cached_submembers->Limit(
314 FIELD => 'ImmediateParentId',
316 VALUE => $self->GroupObj->Id
323 while ( my $item_to_del = $cached_submembers->Next() ) {
324 my $del_err = $item_to_del->Delete();
326 $RT::Handle->Rollback();
327 $RT::Logger->warning("Couldn't delete cached group submember ".$item_to_del->Id);
332 my ($err, $msg) = $self->SUPER::Delete();
334 $RT::Logger->warning("Couldn't delete cached group submember ".$self->Id);
335 $RT::Handle->Rollback();
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();
343 $RT::Handle->Commit();
352 Returns an RT::Principal object for the Principal specified by $self->MemberId
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);
362 return ( $self->{'Member_obj'} );
369 Returns an RT::Principal object for the Group specified in $self->GroupId
375 unless ( defined( $self->{'Group_obj'} ) ) {
376 $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser );
377 $self->{'Group_obj'}->Load( $self->GroupId );
379 return ( $self->{'Group_obj'} );
389 Returns the current value of id.
390 (In the database, id is stored as int(11).)
398 Returns the current value of GroupId.
399 (In the database, GroupId is stored as int(11).)
403 =head2 SetGroupId VALUE
406 Set GroupId to VALUE.
407 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
408 (In the database, GroupId will be stored as a int(11).)
416 Returns the current value of MemberId.
417 (In the database, MemberId is stored as int(11).)
421 =head2 SetMemberId VALUE
424 Set MemberId to VALUE.
425 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
426 (In the database, MemberId will be stored as a int(11).)
434 Returns the current value of Creator.
435 (In the database, Creator is stored as int(11).)
443 Returns the current value of Created.
444 (In the database, Created is stored as datetime.)
452 Returns the current value of LastUpdatedBy.
453 (In the database, LastUpdatedBy is stored as int(11).)
461 Returns the current value of LastUpdated.
462 (In the database, LastUpdated is stored as datetime.)
469 sub _CoreAccessible {
473 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
475 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
477 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
479 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
481 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
483 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
485 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
490 RT::Base->_ImportOverlays();