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
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,
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 .
113 my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser );
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 );
119 SUBCLAUSE => 'filter', # dont't mess up with prev condition
122 VALUE => 'main.GroupId',
124 ENTRYAGGREGATOR => 'AND',
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;
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
140 unless ($other_cached_id) {
141 $RT::Logger->err( "Couldn't add " . $self->MemberId
142 . " as a submember of a supergroup" );
155 InsideTransaction => undef,
159 unless ($args{'Group'} &&
160 UNIVERSAL::isa($args{'Group'}, 'RT::Principal') &&
161 $args{'Group'}->Id ) {
163 $RT::Logger->warning("GroupMember::Create called with a bogus Group arg");
167 unless($args{'Group'}->IsGroup) {
168 $RT::Logger->warning("Someone tried to add a member to a user instead of a group");
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");
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();
184 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
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
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'});
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'});
206 my $id = $self->SUPER::Create(
207 GroupId => $args{'Group'}->Id,
208 MemberId => $args{'Member'}->Id
212 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
216 my $clone = RT::GroupMember->new( $self->CurrentUser );
218 my $cached_id = $clone->_InsertCGM;
220 unless ($cached_id) {
221 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
225 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
232 =head2 _StashUser PRINCIPAL
234 Create { Group => undef, Member => undef }
236 Creates an entry in the groupmembers table, which lists a user
237 as a member of himself. This makes ACL checks a whole bunch easier.
238 This happens once on user create and never ever gets yanked out.
240 PRINCIPAL is expected to be an RT::Principal object for a user
242 This routine expects to be called inside a transaction by RT::User->Create
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();
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
264 my $id = $self->SUPER::Create(
265 GroupId => $args{'Group'}->Id,
266 MemberId => $args{'Member'}->Id,
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'},
281 unless ($cached_id) {
292 Takes no arguments. deletes the currently loaded member from the
295 Expects to be called _outside_ a transaction
303 $RT::Handle->BeginTransaction();
305 # Find all occurrences of this member as a member of this group
306 # in the cache and nuke them, recursively.
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
314 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
316 $cached_submembers->Limit(
319 VALUE => $self->MemberObj->Id
322 $cached_submembers->Limit(
323 FIELD => 'ImmediateParentId',
325 VALUE => $self->GroupObj->Id
332 while ( my $item_to_del = $cached_submembers->Next() ) {
333 my $del_err = $item_to_del->Delete();
335 $RT::Handle->Rollback();
336 $RT::Logger->warning("Couldn't delete cached group submember ".$item_to_del->Id);
341 my ($err, $msg) = $self->SUPER::Delete();
343 $RT::Logger->warning("Couldn't delete cached group submember ".$self->Id);
344 $RT::Handle->Rollback();
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();
352 $RT::Handle->Commit();
361 Returns an RT::Principal object for the Principal specified by $self->MemberId
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);
371 return ( $self->{'Member_obj'} );
378 Returns an RT::Principal object for the Group specified in $self->GroupId
384 unless ( defined( $self->{'Group_obj'} ) ) {
385 $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser );
386 $self->{'Group_obj'}->Load( $self->GroupId );
388 return ( $self->{'Group_obj'} );
398 Returns the current value of id.
399 (In the database, id is stored as int(11).)
407 Returns the current value of GroupId.
408 (In the database, GroupId is stored as int(11).)
412 =head2 SetGroupId VALUE
415 Set GroupId to VALUE.
416 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
417 (In the database, GroupId will be stored as a int(11).)
425 Returns the current value of MemberId.
426 (In the database, MemberId is stored as int(11).)
430 =head2 SetMemberId VALUE
433 Set MemberId to VALUE.
434 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
435 (In the database, MemberId will be stored as a int(11).)
443 Returns the current value of Creator.
444 (In the database, Creator is stored as int(11).)
452 Returns the current value of Created.
453 (In the database, Created is stored as datetime.)
461 Returns the current value of LastUpdatedBy.
462 (In the database, LastUpdatedBy is stored as int(11).)
470 Returns the current value of LastUpdated.
471 (In the database, LastUpdated is stored as datetime.)
478 sub _CoreAccessible {
482 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
484 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
486 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
488 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
490 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
492 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
494 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
499 sub FindDependencies {
501 my ($walker, $deps) = @_;
503 $self->SUPER::FindDependencies($walker, $deps);
505 $deps->Add( out => $self->GroupObj->Object );
506 $deps->Add( out => $self->MemberObj->Object );
513 Dependencies => undef,
516 my $deps = $args{'Dependencies'};
519 my $objs = RT::CachedGroupMembers->new( $self->CurrentUser );
520 $objs->Limit( FIELD => 'MemberId', VALUE => $self->MemberId );
521 $objs->Limit( FIELD => 'ImmediateParentId', VALUE => $self->GroupId );
522 push( @$list, $objs );
524 $deps->_PushDependencies(
526 Flags => RT::Shredder::Constants::DEPENDS_ON,
527 TargetObjects => $list,
528 Shredder => $args{'Shredder'}
531 my $group = $self->GroupObj->Object;
532 # XXX: If we delete member of the ticket owner role group then we should also
533 # fix ticket object, but only if we don't plan to delete group itself!
534 unless( ($group->Name || '') eq 'Owner' &&
535 ($group->Domain || '') eq 'RT::Ticket-Role' ) {
536 return $self->SUPER::__DependsOn( %args );
539 # we don't delete group, so we have to fix Ticket and Group
540 $deps->_PushDependencies(
542 Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::VARIABLE,
543 TargetObjects => $group,
544 Shredder => $args{'Shredder'}
546 $args{'Shredder'}->PutResolver(
547 BaseClass => ref $self,
548 TargetClass => ref $group,
551 my $group = $args{'TargetObject'};
552 return if $args{'Shredder'}->GetState( Object => $group )
553 & (RT::Shredder::Constants::WIPED|RT::Shredder::Constants::IN_WIPING);
554 return unless ($group->Name || '') eq 'Owner';
555 return unless ($group->Domain || '') eq 'RT::Ticket-Role';
557 return if $group->MembersObj->Count > 1;
559 my $group_member = $args{'BaseObject'};
561 if( $group_member->MemberObj->id == RT->Nobody->id ) {
562 RT::Shredder::Exception->throw( "Couldn't delete Nobody from owners role group" );
565 my( $status, $msg ) = $group->AddMember( RT->Nobody->id );
567 RT::Shredder::Exception->throw( $msg ) unless $status;
573 return $self->SUPER::__DependsOn( %args );
578 my ($importer, $uid, $data) = @_;
580 $class->SUPER::PreInflate( $importer, $uid, $data );
582 my $obj = RT::GroupMember->new( RT->SystemUser );
584 GroupId => $data->{GroupId},
585 MemberId => $data->{MemberId},
588 $importer->Resolve( $uid => ref($obj) => $obj->Id );
601 RT::Base->_ImportOverlays();