2 # {{{ BEGIN BPS TAGGED BLOCK
6 # This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
7 # <jesse@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 # General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 # CONTRIBUTION SUBMISSION POLICY:
31 # (The following paragraph is not intended to limit the rights granted
32 # to you to modify and distribute this software under the terms of
33 # the GNU General Public License and is only of importance to you if
34 # you choose to contribute your changes and enhancements to the
35 # community by submitting them to Best Practical Solutions, LLC.)
37 # By intentionally submitting any modifications, corrections or
38 # derivatives to this work, or any other work intended for use with
39 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
40 # you are the copyright holder for those contributions and you grant
41 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
42 # royalty-free, perpetual, license to use, copy, create derivative
43 # works based on those contributions, and sublicense and distribute
44 # those contributions and any derivatives thereof.
46 # }}} END BPS TAGGED BLOCK
47 # Released under the terms of version 2 of the GNU Public License
51 RT::Group - RT\'s group object
56 my $group = new RT::Group($CurrentUser);
64 Jesse Vincent, jesse@bestpractical.com
76 ok (require RT::Group);
78 ok (my $group = RT::Group->new($RT::SystemUser), "instantiated a group object");
79 ok (my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'TestGroup', Description => 'A test group',
80 ), 'Created a new group');
81 ok ($id != 0, "Group id is $id");
82 ok ($group->Name eq 'TestGroup', "The group's name is 'TestGroup'");
83 my $ng = RT::Group->new($RT::SystemUser);
85 ok($ng->LoadUserDefinedGroup('TestGroup'), "Loaded testgroup");
86 ok(($ng->id == $group->id), "Loaded the right group");
89 ok (($id,$msg) = $ng->AddMember('1'), "Added a member to the group");
91 ok (($id,$msg) = $ng->AddMember('2' ), "Added a member to the group");
93 ok (($id,$msg) = $ng->AddMember('3' ), "Added a member to the group");
96 # Group 1 now has members 1, 2 ,3
98 my $group_2 = RT::Group->new($RT::SystemUser);
99 ok (my ($id_2, $msg_2) = $group_2->CreateUserDefinedGroup( Name => 'TestGroup2', Description => 'A second test group'), , 'Created a new group');
100 ok ($id_2 != 0, "Created group 2 ok- $msg_2 ");
101 ok (($id,$msg) = $group_2->AddMember($ng->PrincipalId), "Made TestGroup a member of testgroup2");
103 ok (($id,$msg) = $group_2->AddMember('1' ), "Added member RT_System to the group TestGroup2");
106 # Group 2 how has 1, g1->{1, 2,3}
108 my $group_3 = RT::Group->new($RT::SystemUser);
109 ok (($id_3, $msg) = $group_3->CreateUserDefinedGroup( Name => 'TestGroup3', Description => 'A second test group'), 'Created a new group');
110 ok ($id_3 != 0, "Created group 3 ok - $msg");
111 ok (($id,$msg) =$group_3->AddMember($group_2->PrincipalId), "Made TestGroup a member of testgroup2");
114 # g3 now has g2->{1, g1->{1,2,3}}
116 my $principal_1 = RT::Principal->new($RT::SystemUser);
117 $principal_1->Load('1');
119 my $principal_2 = RT::Principal->new($RT::SystemUser);
120 $principal_2->Load('2');
122 ok (($id,$msg) = $group_3->AddMember('1' ), "Added member RT_System to the group TestGroup2");
125 # g3 now has 1, g2->{1, g1->{1,2,3}}
127 ok($group_3->HasMember($principal_2) == undef, "group 3 doesn't have member 2");
128 ok($group_3->HasMemberRecursively($principal_2), "group 3 has member 2 recursively");
129 ok($ng->HasMember($principal_2) , "group ".$ng->Id." has member 2");
130 my ($delid , $delmsg) =$ng->DeleteMember($principal_2->Id);
131 ok ($delid !=0, "Sucessfully deleted it-".$delid."-".$delmsg);
133 #Gotta reload the group objects, since we've been messing with various internals.
134 # we shouldn't need to do this.
135 #$ng->LoadUserDefinedGroup('TestGroup');
136 #$group_2->LoadUserDefinedGroup('TestGroup2');
137 #$group_3->LoadUserDefinedGroup('TestGroup');
140 # Group 2 how has 1, g1->{1, 3}
141 # g3 now has 1, g2->{1, g1->{1, 3}}
143 ok(!$ng->HasMember($principal_2) , "group ".$ng->Id." no longer has member 2");
144 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 doesn't have member 2");
145 ok($group_2->HasMemberRecursively($principal_2) == undef, "group 2 doesn't have member 2");
146 ok($ng->HasMember($principal_2) == undef, "group 1 doesn't have member 2");;
147 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 has member 2 recursively");
158 no warnings qw(redefine);
161 use RT::GroupMembers;
165 use vars qw/$RIGHTS/;
168 AdminGroup => 'Modify group metadata or delete group', # loc_pair
169 AdminGroupMembership =>
170 'Modify membership roster for this group', # loc_pair
171 ModifyOwnMembership => 'Join or leave this group', # loc_pair
172 EditSavedSearches => 'Edit saved searches for this group', # loc_pair
173 ShowSavedSearches => 'Display saved searches for this group', # loc_pair
176 # Tell RT::ACE that this sort of object can get acls granted
177 $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
182 # TODO: This should be refactored out into an RT::ACLedObject or something
183 # stuff the rights into a hash of rights that can exist.
185 foreach my $right ( keys %{$RIGHTS} ) {
186 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
190 =head2 AvailableRights
192 Returns a hash of available rights for this object. The keys are the right names and the values are a description of what the rights do
196 sub AvailableRights {
202 # {{{ sub SelfDescription
204 =head2 SelfDescription
206 Returns a user-readable description of what this group is for and what it's named.
210 sub SelfDescription {
212 if ($self->Domain eq 'ACLEquivalence') {
213 my $user = RT::Principal->new($self->CurrentUser);
214 $user->Load($self->Instance);
215 return $self->loc("user [_1]",$user->Object->Name);
217 elsif ($self->Domain eq 'UserDefined') {
218 return $self->loc("group '[_1]'",$self->Name);
220 elsif ($self->Domain eq 'Personal') {
221 my $user = RT::User->new($self->CurrentUser);
222 $user->Load($self->Instance);
223 return $self->loc("personal group '[_1]' for user '[_2]'",$self->Name, $user->Name);
225 elsif ($self->Domain eq 'RT::System-Role') {
226 return $self->loc("system [_1]",$self->Type);
228 elsif ($self->Domain eq 'RT::Queue-Role') {
229 my $queue = RT::Queue->new($self->CurrentUser);
230 $queue->Load($self->Instance);
231 return $self->loc("queue [_1] [_2]",$queue->Name, $self->Type);
233 elsif ($self->Domain eq 'RT::Ticket-Role') {
234 return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Type);
236 elsif ($self->Domain eq 'SystemInternal') {
237 return $self->loc("system group '[_1]'",$self->Type);
240 return $self->loc("undescribed group [_1]",$self->Id);
250 Load a group object from the database. Takes a single argument.
251 If the argument is numerical, load by the column 'id'. Otherwise,
258 my $identifier = shift || return undef;
260 #if it's an int, load by id. otherwise, load by name.
261 if ( $identifier !~ /\D/ ) {
262 $self->SUPER::LoadById($identifier);
265 $RT::Logger->crit("Group -> Load called with a bogus argument");
272 # {{{ sub LoadUserDefinedGroup
274 =head2 LoadUserDefinedGroup NAME
276 Loads a system group from the database. The only argument is
282 sub LoadUserDefinedGroup {
284 my $identifier = shift;
286 $self->LoadByCols( "Domain" => 'UserDefined',
287 "Name" => $identifier );
292 # {{{ sub LoadACLEquivalenceGroup
294 =head2 LoadACLEquivalenceGroup PRINCIPAL
296 Loads a user's acl equivalence group. Takes a principal object.
297 ACL equivalnce groups are used to simplify the acl system. Each user
298 has one group that only he is a member of. Rights granted to the user
299 are actually granted to that group. This greatly simplifies ACL checks.
300 While this results in a somewhat more complex setup when creating users
301 and granting ACLs, it _greatly_ simplifies acl checks.
307 sub LoadACLEquivalenceGroup {
311 $self->LoadByCols( "Domain" => 'ACLEquivalence',
312 "Type" => 'UserEquiv',
313 "Instance" => $princ->Id);
318 # {{{ sub LoadPersonalGroup
320 =head2 LoadPersonalGroup {Name => NAME, User => USERID}
322 Loads a personal group from the database.
326 sub LoadPersonalGroup {
328 my %args = ( Name => undef,
332 $self->LoadByCols( "Domain" => 'Personal',
333 "Instance" => $args{'User'},
335 "Name" => $args{'Name'} );
340 # {{{ sub LoadSystemInternalGroup
342 =head2 LoadSystemInternalGroup NAME
344 Loads a Pseudo group from the database. The only argument is
350 sub LoadSystemInternalGroup {
352 my $identifier = shift;
354 $self->LoadByCols( "Domain" => 'SystemInternal',
355 "Type" => $identifier );
360 # {{{ sub LoadTicketRoleGroup
362 =head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Type => TYPE }
364 Loads a ticket group from the database.
366 Takes a param hash with 2 parameters:
368 Ticket is the TicketId we're curious about
369 Type is the type of Group we're trying to load:
370 Requestor, Cc, AdminCc, Owner
374 sub LoadTicketRoleGroup {
376 my %args = (Ticket => '0',
379 $self->LoadByCols( Domain => 'RT::Ticket-Role',
380 Instance =>$args{'Ticket'},
381 Type => $args{'Type'}
387 # {{{ sub LoadQueueRoleGroup
389 =head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
391 Loads a Queue group from the database.
393 Takes a param hash with 2 parameters:
395 Queue is the QueueId we're curious about
396 Type is the type of Group we're trying to load:
397 Requestor, Cc, AdminCc, Owner
401 sub LoadQueueRoleGroup {
403 my %args = (Queue => undef,
406 $self->LoadByCols( Domain => 'RT::Queue-Role',
407 Instance =>$args{'Queue'},
408 Type => $args{'Type'}
414 # {{{ sub LoadSystemRoleGroup
416 =head2 LoadSystemRoleGroup Type
418 Loads a System group from the database.
420 Takes a single param: Type
422 Type is the type of Group we're trying to load:
423 Requestor, Cc, AdminCc, Owner
427 sub LoadSystemRoleGroup {
430 $self->LoadByCols( Domain => 'RT::System-Role',
440 You need to specify what sort of group you're creating by calling one of the other
441 Create_____ routines.
447 $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
448 return(0,$self->loc('Permission Denied'));
457 Takes a paramhash with named arguments: Name, Description.
459 Returns a tuple of (Id, Message). If id is 0, the create failed
467 Description => undef,
471 InsideTransaction => undef,
475 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
476 # Groups deal with principal ids, rather than user ids.
477 # When creating this group, set up a principal Id for it.
478 my $principal = RT::Principal->new( $self->CurrentUser );
479 my $principal_id = $principal->Create(
480 PrincipalType => 'Group',
483 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
486 $self->SUPER::Create(
488 Name => $args{'Name'},
489 Description => $args{'Description'},
490 Type => $args{'Type'},
491 Domain => $args{'Domain'},
492 Instance => ($args{'Instance'} || '0')
496 return ( 0, $self->loc('Could not create group') );
499 # If we couldn't create a principal Id, get the fuck out.
500 unless ($principal_id) {
501 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
502 $self->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
503 return ( 0, $self->loc('Could not create group') );
506 # Now we make the group a member of itself as a cached group member
507 # this needs to exist so that group ACL checks don't fall over.
508 # you're checking CachedGroupMembers to see if the principal in question
509 # is a member of the principal the rights have been granted too
511 # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
512 # cached members. thankfully, we're creating the group now...so it has no members.
513 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
514 $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
518 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
519 return ( $id, $self->loc("Group created") );
524 # {{{ CreateUserDefinedGroup
526 =head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
528 A helper subroutine which creates a system group
530 Returns a tuple of (Id, Message). If id is 0, the create failed
534 sub CreateUserDefinedGroup {
537 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
538 $RT::Logger->warning( $self->CurrentUser->Name
539 . " Tried to create a group without permission." );
540 return ( 0, $self->loc('Permission Denied') );
543 return($self->_Create( Domain => 'UserDefined', Type => '', Instance => '', @_));
548 # {{{ _CreateACLEquivalenceGroup
550 =head2 _CreateACLEquivalenceGroup { Principal }
552 A helper subroutine which creates a group containing only
553 an individual user. This gets used by the ACL system to check rights.
554 Yes, it denormalizes the data, but that's ok, as we totally win on performance.
556 Returns a tuple of (Id, Message). If id is 0, the create failed
560 sub _CreateACLEquivalenceGroup {
564 my $id = $self->_Create( Domain => 'ACLEquivalence',
566 Name => 'User '. $princ->Object->Id,
567 Description => 'ACL equiv. for user '.$princ->Object->Id,
568 Instance => $princ->Id,
569 InsideTransaction => 1);
571 $RT::Logger->crit("Couldn't create ACL equivalence group");
575 # We use stashuser so we don't get transactions inside transactions
576 # and so we bypass all sorts of cruft we don't need
577 my $aclstash = RT::GroupMember->new($self->CurrentUser);
578 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
582 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
583 # We call super delete so we don't get acl checked.
584 $self->SUPER::Delete();
592 # {{{ CreatePersonalGroup
594 =head2 CreatePersonalGroup { PrincipalId => PRINCIPAL_ID, Name => "name", Description => "Description"}
596 A helper subroutine which creates a personal group. Generally,
597 personal groups are used for ACL delegation and adding to ticket roles
598 PrincipalId defaults to the current user's principal id.
600 Returns a tuple of (Id, Message). If id is 0, the create failed
604 sub CreatePersonalGroup {
608 Description => undef,
609 PrincipalId => $self->CurrentUser->PrincipalId,
613 if ( $self->CurrentUser->PrincipalId == $args{'PrincipalId'} ) {
615 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups') ) {
616 $RT::Logger->warning( $self->CurrentUser->Name
617 . " Tried to create a group without permission." );
618 return ( 0, $self->loc('Permission Denied') );
623 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
624 $RT::Logger->warning( $self->CurrentUser->Name
625 . " Tried to create a group without permission." );
626 return ( 0, $self->loc('Permission Denied') );
633 Domain => 'Personal',
635 Instance => $args{'PrincipalId'},
636 Name => $args{'Name'},
637 Description => $args{'Description'}
644 # {{{ CreateRoleGroup
646 =head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
648 A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
649 Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
650 Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
651 Instance is the id of the ticket or queue in question
653 This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
655 Returns a tuple of (Id, Message). If id is 0, the create failed
659 sub CreateRoleGroup {
661 my %args = ( Instance => undef,
665 unless ( $args{'Type'} =~ /^(?:Cc|AdminCc|Requestor|Owner)$/ ) {
666 return ( 0, $self->loc("Invalid Group Type") );
670 return ( $self->_Create( Domain => $args{'Domain'},
671 Instance => $args{'Instance'},
672 Type => $args{'Type'},
673 InsideTransaction => 1 ) );
689 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
690 return ( 0, 'Permission Denied' );
693 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
696 # Remove the principal object
697 # Remove this group from anything it's a member of.
698 # Remove all cached members of this group
699 # Remove any rights granted to this group
700 # remove any rights delegated by way of this group
702 return ( $self->SUPER::Delete(@_) );
707 =head2 SetDisabled BOOL
709 If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
710 It will not appear in most group listings.
712 This routine finds all the cached group members that are members of this group (recursively) and disables them.
720 if ($self->Domain eq 'Personal') {
721 if ($self->CurrentUser->PrincipalId == $self->Instance) {
722 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
723 return ( 0, $self->loc('Permission Denied') );
726 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
727 return ( 0, $self->loc('Permission Denied') );
732 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
733 return (0, $self->loc('Permission Denied'));
736 $RT::Handle->BeginTransaction();
737 $self->PrincipalObj->SetDisabled($val);
742 # Find all occurrences of this member as a member of this group
743 # in the cache and nuke them, recursively.
745 # The following code will delete all Cached Group members
746 # where this member's group is _not_ the primary group
747 # (Ie if we're deleting C as a member of B, and B happens to be
748 # a member of A, will delete C as a member of A without touching
751 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
753 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
755 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
756 # TODO what about the groups key cache?
757 RT::Principal->_InvalidateACLCache();
761 while ( my $item = $cached_submembers->Next() ) {
762 my $del_err = $item->SetDisabled($val);
764 $RT::Handle->Rollback();
765 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
770 $RT::Handle->Commit();
771 return (1, $self->loc("Succeeded"));
781 $self->PrincipalObj->Disabled(@_);
787 =head2 DeepMembersObj
789 Returns an RT::CachedGroupMembers object of this group's members.
795 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
797 #If we don't have rights, don't include any results
798 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
799 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
801 return ( $members_obj );
809 =head2 UserMembersObj
811 Returns an RT::Users object of this group's members, including
812 all members of subgroups
819 my $users = RT::Users->new($self->CurrentUser);
821 #If we don't have rights, don't include any results
822 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
824 my $cached_members = $users->NewAlias('CachedGroupMembers');
825 $users->Join(ALIAS1 => $cached_members, FIELD1 => 'MemberId',
826 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id');
827 $users->Limit(ALIAS => $cached_members,
830 VALUE => $self->PrincipalId);
842 Returns an RT::CachedGroupMembers object of this group's members.
848 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
850 #If we don't have rights, don't include any results
851 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
852 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
854 return ( $members_obj );
860 # {{{ MemberEmailAddresses
862 =head2 MemberEmailAddresses
864 Returns an array of the email addresses of all of this group's members
869 sub MemberEmailAddresses {
873 my $members = $self->UserMembersObj();
874 while (my $member = $members->Next) {
875 $addresses{$member->EmailAddress} = 1;
877 return(sort keys %addresses);
882 # {{{ MemberEmailAddressesAsString
884 =head2 MemberEmailAddressesAsString
886 Returns a comma delimited string of the email addresses of all users
887 who are members of this group.
892 sub MemberEmailAddressesAsString {
894 return (join(', ', $self->MemberEmailAddresses));
901 =head2 AddMember PRINCIPAL_ID
903 AddMember adds a principal to this group. It takes a single principal id.
904 Returns a two value array. the first value is true on successful
905 addition or 0 on failure. The second value is a textual status msg.
911 my $new_member = shift;
915 if ($self->Domain eq 'Personal') {
916 if ($self->CurrentUser->PrincipalId == $self->Instance) {
917 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
918 return ( 0, $self->loc('Permission Denied') );
921 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
922 return ( 0, $self->loc('Permission Denied') );
928 # We should only allow membership changes if the user has the right
929 # to modify group membership or the user is the principal in question
930 # and the user has the right to modify his own membership
931 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
932 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
933 $self->CurrentUserHasRight('AdminGroupMembership') ) {
934 #User has no permission to be doing this
935 return ( 0, $self->loc("Permission Denied") );
939 $self->_AddMember(PrincipalId => $new_member);
942 # A helper subroutine for AddMember that bypasses the ACL checks
943 # this should _ONLY_ ever be called from Ticket/Queue AddWatcher
944 # when we want to deal with groups according to queue rights
945 # In the dim future, this will all get factored out and life
948 # takes a paramhash of { PrincipalId => undef, InsideTransaction }
952 my %args = ( PrincipalId => undef,
953 InsideTransaction => undef,
955 my $new_member = $args{'PrincipalId'};
958 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
959 return(0, $self->loc("Group not found"));
962 unless ($new_member =~ /^\d+$/) {
963 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
967 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
968 $new_member_obj->Load($new_member);
971 unless ( $new_member_obj->Id ) {
972 $RT::Logger->debug("Couldn't find that principal");
973 return ( 0, $self->loc("Couldn't find that principal") );
976 if ( $self->HasMember( $new_member_obj ) ) {
978 #User is already a member of this group. no need to add it
979 return ( 0, $self->loc("Group already has member") );
981 if ( $new_member_obj->IsGroup &&
982 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
984 #This group can't be made to be a member of itself
985 return ( 0, $self->loc("Groups can't be members of their members"));
989 my $member_object = RT::GroupMember->new( $self->CurrentUser );
990 my $id = $member_object->Create(
991 Member => $new_member_obj,
992 Group => $self->PrincipalObj,
993 InsideTransaction => $args{'InsideTransaction'}
996 return ( 1, $self->loc("Member added") );
999 return(0, $self->loc("Couldn't add member to group"));
1006 =head2 HasMember RT::Principal
1008 Takes an RT::Principal object returns a GroupMember Id if that user is a
1009 member of this group.
1010 Returns undef if the user isn't a member of the group or if the current
1011 user doesn't have permission to find out. Arguably, it should differentiate
1012 between ACL failure and non membership.
1018 my $principal = shift;
1021 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
1022 $RT::Logger->crit("Group::HasMember was called with an argument that".
1023 "isn't an RT::Principal. It's $principal");
1027 unless ($principal->Id) {
1031 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1032 $member_obj->LoadByCols( MemberId => $principal->id,
1033 GroupId => $self->PrincipalId );
1035 #If we have a member object
1036 if ( defined $member_obj->id ) {
1037 return ( $member_obj->id );
1040 #If Load returns no objects, we have an undef id.
1042 #$RT::Logger->debug($self." does not contain principal ".$principal->id);
1049 # {{{ HasMemberRecursively
1051 =head2 HasMemberRecursively RT::Principal
1053 Takes an RT::Principal object and returns true if that user is a member of
1055 Returns undef if the user isn't a member of the group or if the current
1056 user doesn't have permission to find out. Arguably, it should differentiate
1057 between ACL failure and non membership.
1061 sub HasMemberRecursively {
1063 my $principal = shift;
1065 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
1066 $RT::Logger->crit("Group::HasMemberRecursively was called with an argument that".
1067 "isn't an RT::Principal. It's $principal");
1070 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1071 $member_obj->LoadByCols( MemberId => $principal->Id,
1072 GroupId => $self->PrincipalId ,
1076 #If we have a member object
1077 if ( defined $member_obj->id ) {
1081 #If Load returns no objects, we have an undef id.
1091 =head2 DeleteMember PRINCIPAL_ID
1093 Takes the principal id of a current user or group.
1094 If the current user has apropriate rights,
1095 removes that GroupMember from this group.
1096 Returns a two value array. the first value is true on successful
1097 addition or 0 on failure. The second value is a textual status msg.
1103 my $member_id = shift;
1106 # We should only allow membership changes if the user has the right
1107 # to modify group membership or the user is the principal in question
1108 # and the user has the right to modify his own membership
1110 if ($self->Domain eq 'Personal') {
1111 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1112 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1113 return ( 0, $self->loc('Permission Denied') );
1116 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1117 return ( 0, $self->loc('Permission Denied') );
1122 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
1123 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1124 $self->CurrentUserHasRight('AdminGroupMembership') ) {
1125 #User has no permission to be doing this
1126 return ( 0, $self->loc("Permission Denied") );
1129 $self->_DeleteMember($member_id);
1132 # A helper subroutine for DeleteMember that bypasses the ACL checks
1133 # this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1134 # when we want to deal with groups according to queue rights
1135 # In the dim future, this will all get factored out and life
1140 my $member_id = shift;
1142 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1144 $member_obj->LoadByCols( MemberId => $member_id,
1145 GroupId => $self->PrincipalId);
1148 #If we couldn't load it, return undef.
1149 unless ( $member_obj->Id() ) {
1150 $RT::Logger->debug("Group has no member with that id");
1151 return ( 0,$self->loc( "Group has no such member" ));
1154 #Now that we've checked ACLs and sanity, delete the groupmember
1155 my $val = $member_obj->Delete();
1158 return ( $val, $self->loc("Member deleted") );
1161 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1162 return ( 0, $self->loc("Member not deleted" ));
1168 # {{{ ACL Related routines
1174 if ($self->Domain eq 'Personal') {
1175 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1176 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1177 return ( 0, $self->loc('Permission Denied') );
1180 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1181 return ( 0, $self->loc('Permission Denied') );
1186 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
1187 return ( 0, $self->loc('Permission Denied') );
1190 return ( $self->SUPER::_Set(@_) );
1198 =item CurrentUserHasRight RIGHTNAME
1200 Returns true if the current user has the specified right for this group.
1203 TODO: we don't deal with membership visibility yet
1208 sub CurrentUserHasRight {
1215 $self->CurrentUser->HasRight( Object => $self,
1216 Right => $right )) {
1219 elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
1232 # {{{ Principal related routines
1236 Returns the principal object for this user. returns an empty RT::Principal
1237 if there's no principal object matching this user.
1238 The response is cached. PrincipalObj should never ever change.
1242 ok(my $u = RT::Group->new($RT::SystemUser));
1243 ok($u->Load(4), "Loaded the first user");
1244 ok($u->PrincipalObj->ObjectId == 4, "user 4 is the fourth principal");
1245 ok($u->PrincipalObj->PrincipalType eq 'Group' , "Principal 4 is a group");
1254 unless ($self->{'PrincipalObj'} &&
1255 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1256 ($self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
1258 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1259 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1260 'PrincipalType' => 'Group') ;
1262 return($self->{'PrincipalObj'});
1268 Returns this user's PrincipalId