4 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
6 # (Except where explictly superceded by other copyright notices)
8 # This work is made available to you under the terms of Version 2 of
9 # the GNU General Public License. A copy of that license should have
10 # been provided with this software, but in any event can be snarfed
13 # This work is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
18 # Unless otherwise specified, all modifications, corrections or
19 # extensions to this work which alter its source code become the
20 # property of Best Practical Solutions, LLC when submitted for
21 # inclusion in the work.
25 # Released under the terms of version 2 of the GNU Public License
29 RT::Group - RT\'s group object
34 my $group = new RT::Group($CurrentUser);
42 Jesse Vincent, jesse@bestpractical.com
54 ok (require RT::Group);
56 ok (my $group = RT::Group->new($RT::SystemUser), "instantiated a group object");
57 ok (my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'TestGroup', Description => 'A test group',
58 ), 'Created a new group');
59 ok ($id != 0, "Group id is $id");
60 ok ($group->Name eq 'TestGroup', "The group's name is 'TestGroup'");
61 my $ng = RT::Group->new($RT::SystemUser);
63 ok($ng->LoadUserDefinedGroup('TestGroup'), "Loaded testgroup");
64 ok(($ng->id == $group->id), "Loaded the right group");
67 ok (($id,$msg) = $ng->AddMember('1'), "Added a member to the group");
69 ok (($id,$msg) = $ng->AddMember('2' ), "Added a member to the group");
71 ok (($id,$msg) = $ng->AddMember('3' ), "Added a member to the group");
74 # Group 1 now has members 1, 2 ,3
76 my $group_2 = RT::Group->new($RT::SystemUser);
77 ok (my ($id_2, $msg_2) = $group_2->CreateUserDefinedGroup( Name => 'TestGroup2', Description => 'A second test group'), , 'Created a new group');
78 ok ($id_2 != 0, "Created group 2 ok- $msg_2 ");
79 ok (($id,$msg) = $group_2->AddMember($ng->PrincipalId), "Made TestGroup a member of testgroup2");
81 ok (($id,$msg) = $group_2->AddMember('1' ), "Added member RT_System to the group TestGroup2");
84 # Group 2 how has 1, g1->{1, 2,3}
86 my $group_3 = RT::Group->new($RT::SystemUser);
87 ok (($id_3, $msg) = $group_3->CreateUserDefinedGroup( Name => 'TestGroup3', Description => 'A second test group'), 'Created a new group');
88 ok ($id_3 != 0, "Created group 3 ok - $msg");
89 ok (($id,$msg) =$group_3->AddMember($group_2->PrincipalId), "Made TestGroup a member of testgroup2");
92 # g3 now has g2->{1, g1->{1,2,3}}
94 my $principal_1 = RT::Principal->new($RT::SystemUser);
95 $principal_1->Load('1');
97 my $principal_2 = RT::Principal->new($RT::SystemUser);
98 $principal_2->Load('2');
100 ok (($id,$msg) = $group_3->AddMember('1' ), "Added member RT_System to the group TestGroup2");
103 # g3 now has 1, g2->{1, g1->{1,2,3}}
105 ok($group_3->HasMember($principal_2) == undef, "group 3 doesn't have member 2");
106 ok($group_3->HasMemberRecursively($principal_2), "group 3 has member 2 recursively");
107 ok($ng->HasMember($principal_2) , "group ".$ng->Id." has member 2");
108 my ($delid , $delmsg) =$ng->DeleteMember($principal_2->Id);
109 ok ($delid !=0, "Sucessfully deleted it-".$delid."-".$delmsg);
111 #Gotta reload the group objects, since we've been messing with various internals.
112 # we shouldn't need to do this.
113 #$ng->LoadUserDefinedGroup('TestGroup');
114 #$group_2->LoadUserDefinedGroup('TestGroup2');
115 #$group_3->LoadUserDefinedGroup('TestGroup');
118 # Group 2 how has 1, g1->{1, 3}
119 # g3 now has 1, g2->{1, g1->{1, 3}}
121 ok(!$ng->HasMember($principal_2) , "group ".$ng->Id." no longer has member 2");
122 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 doesn't have member 2");
123 ok($group_2->HasMemberRecursively($principal_2) == undef, "group 2 doesn't have member 2");
124 ok($ng->HasMember($principal_2) == undef, "group 1 doesn't have member 2");;
125 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 has member 2 recursively");
136 no warnings qw(redefine);
139 use RT::GroupMembers;
143 use vars qw/$RIGHTS/;
146 AdminGroup => 'Modify group metadata or delete group', # loc_pair
147 AdminGroupMembership =>
148 'Modify membership roster for this group', # loc_pair
149 ModifyOwnMembership => 'Join or leave this group' # loc_pair
152 # Tell RT::ACE that this sort of object can get acls granted
153 $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
158 # TODO: This should be refactored out into an RT::ACLedObject or something
159 # stuff the rights into a hash of rights that can exist.
161 foreach my $right ( keys %{$RIGHTS} ) {
162 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
166 =head2 AvailableRights
168 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
172 sub AvailableRights {
178 # {{{ sub SelfDescription
180 =head2 SelfDescription
182 Returns a user-readable description of what this group is for and what it's named.
186 sub SelfDescription {
188 if ($self->Domain eq 'ACLEquivalence') {
189 my $user = RT::Principal->new($self->CurrentUser);
190 $user->Load($self->Instance);
191 return $self->loc("user [_1]",$user->Object->Name);
193 elsif ($self->Domain eq 'UserDefined') {
194 return $self->loc("group '[_1]'",$self->Name);
196 elsif ($self->Domain eq 'Personal') {
197 my $user = RT::User->new($self->CurrentUser);
198 $user->Load($self->Instance);
199 return $self->loc("personal group '[_1]' for user '[_2]'",$self->Name, $user->Name);
201 elsif ($self->Domain eq 'RT::System-Role') {
202 return $self->loc("system [_1]",$self->Type);
204 elsif ($self->Domain eq 'RT::Queue-Role') {
205 my $queue = RT::Queue->new($self->CurrentUser);
206 $queue->Load($self->Instance);
207 return $self->loc("queue [_1] [_2]",$queue->Name, $self->Type);
209 elsif ($self->Domain eq 'RT::Ticket-Role') {
210 return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Type);
212 elsif ($self->Domain eq 'SystemInternal') {
213 return $self->loc("system group '[_1]'",$self->Type);
216 return $self->loc("undescribed group [_1]",$self->Id);
226 Load a group object from the database. Takes a single argument.
227 If the argument is numerical, load by the column 'id'. Otherwise,
234 my $identifier = shift || return undef;
236 #if it's an int, load by id. otherwise, load by name.
237 if ( $identifier !~ /\D/ ) {
238 $self->SUPER::LoadById($identifier);
241 $RT::Logger->crit("Group -> Load called with a bogus argument");
248 # {{{ sub LoadUserDefinedGroup
250 =head2 LoadUserDefinedGroup NAME
252 Loads a system group from the database. The only argument is
258 sub LoadUserDefinedGroup {
260 my $identifier = shift;
262 $self->LoadByCols( "Domain" => 'UserDefined',
263 "Name" => $identifier );
268 # {{{ sub LoadACLEquivalenceGroup
270 =head2 LoadACLEquivalenceGroup PRINCIPAL
272 Loads a user's acl equivalence group. Takes a principal object.
273 ACL equivalnce groups are used to simplify the acl system. Each user
274 has one group that only he is a member of. Rights granted to the user
275 are actually granted to that group. This greatly simplifies ACL checks.
276 While this results in a somewhat more complex setup when creating users
277 and granting ACLs, it _greatly_ simplifies acl checks.
283 sub LoadACLEquivalenceGroup {
287 $self->LoadByCols( "Domain" => 'ACLEquivalence',
288 "Type" => 'UserEquiv',
289 "Instance" => $princ->Id);
294 # {{{ sub LoadPersonalGroup
296 =head2 LoadPersonalGroup {Name => NAME, User => USERID}
298 Loads a personal group from the database.
302 sub LoadPersonalGroup {
304 my %args = ( Name => undef,
308 $self->LoadByCols( "Domain" => 'Personal',
309 "Instance" => $args{'User'},
311 "Name" => $args{'Name'} );
316 # {{{ sub LoadSystemInternalGroup
318 =head2 LoadSystemInternalGroup NAME
320 Loads a Pseudo group from the database. The only argument is
326 sub LoadSystemInternalGroup {
328 my $identifier = shift;
330 $self->LoadByCols( "Domain" => 'SystemInternal',
331 "Type" => $identifier );
336 # {{{ sub LoadTicketRoleGroup
338 =head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Type => TYPE }
340 Loads a ticket group from the database.
342 Takes a param hash with 2 parameters:
344 Ticket is the TicketId we're curious about
345 Type is the type of Group we're trying to load:
346 Requestor, Cc, AdminCc, Owner
350 sub LoadTicketRoleGroup {
352 my %args = (Ticket => '0',
355 $self->LoadByCols( Domain => 'RT::Ticket-Role',
356 Instance =>$args{'Ticket'},
357 Type => $args{'Type'}
363 # {{{ sub LoadQueueRoleGroup
365 =head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
367 Loads a Queue group from the database.
369 Takes a param hash with 2 parameters:
371 Queue is the QueueId we're curious about
372 Type is the type of Group we're trying to load:
373 Requestor, Cc, AdminCc, Owner
377 sub LoadQueueRoleGroup {
379 my %args = (Queue => undef,
382 $self->LoadByCols( Domain => 'RT::Queue-Role',
383 Instance =>$args{'Queue'},
384 Type => $args{'Type'}
390 # {{{ sub LoadSystemRoleGroup
392 =head2 LoadSystemRoleGroup Type
394 Loads a System group from the database.
396 Takes a single param: Type
398 Type is the type of Group we're trying to load:
399 Requestor, Cc, AdminCc, Owner
403 sub LoadSystemRoleGroup {
406 $self->LoadByCols( Domain => 'RT::System-Role',
416 You need to specify what sort of group you're creating by calling one of the other
417 Create_____ routines.
423 $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
424 return(0,$self->loc('Permission Denied'));
433 Takes a paramhash with named arguments: Name, Description.
435 Returns a tuple of (Id, Message). If id is 0, the create failed
443 Description => undef,
447 InsideTransaction => undef,
451 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
452 # Groups deal with principal ids, rather than user ids.
453 # When creating this group, set up a principal Id for it.
454 my $principal = RT::Principal->new( $self->CurrentUser );
455 my $principal_id = $principal->Create(
456 PrincipalType => 'Group',
459 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
462 $self->SUPER::Create(
464 Name => $args{'Name'},
465 Description => $args{'Description'},
466 Type => $args{'Type'},
467 Domain => $args{'Domain'},
468 Instance => ($args{'Instance'} || '0')
472 return ( 0, $self->loc('Could not create group') );
475 # If we couldn't create a principal Id, get the fuck out.
476 unless ($principal_id) {
477 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
478 $self->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
479 return ( 0, $self->loc('Could not create group') );
482 # Now we make the group a member of itself as a cached group member
483 # this needs to exist so that group ACL checks don't fall over.
484 # you're checking CachedGroupMembers to see if the principal in question
485 # is a member of the principal the rights have been granted too
487 # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
488 # cached members. thankfully, we're creating the group now...so it has no members.
489 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
490 $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
494 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
495 return ( $id, $self->loc("Group created") );
500 # {{{ CreateUserDefinedGroup
502 =head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
504 A helper subroutine which creates a system group
506 Returns a tuple of (Id, Message). If id is 0, the create failed
510 sub CreateUserDefinedGroup {
513 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
514 $RT::Logger->warning( $self->CurrentUser->Name
515 . " Tried to create a group without permission." );
516 return ( 0, $self->loc('Permission Denied') );
519 return($self->_Create( Domain => 'UserDefined', Type => '', Instance => '', @_));
524 # {{{ _CreateACLEquivalenceGroup
526 =head2 _CreateACLEquivalenceGroup { Principal }
528 A helper subroutine which creates a group containing only
529 an individual user. This gets used by the ACL system to check rights.
530 Yes, it denormalizes the data, but that's ok, as we totally win on performance.
532 Returns a tuple of (Id, Message). If id is 0, the create failed
536 sub _CreateACLEquivalenceGroup {
540 my $id = $self->_Create( Domain => 'ACLEquivalence',
542 Name => 'User '. $princ->Object->Id,
543 Description => 'ACL equiv. for user '.$princ->Object->Id,
544 Instance => $princ->Id,
545 InsideTransaction => 1);
547 $RT::Logger->crit("Couldn't create ACL equivalence group");
551 # We use stashuser so we don't get transactions inside transactions
552 # and so we bypass all sorts of cruft we don't need
553 my $aclstash = RT::GroupMember->new($self->CurrentUser);
554 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
558 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
559 # We call super delete so we don't get acl checked.
560 $self->SUPER::Delete();
568 # {{{ CreatePersonalGroup
570 =head2 CreatePersonalGroup { PrincipalId => PRINCIPAL_ID, Name => "name", Description => "Description"}
572 A helper subroutine which creates a personal group. Generally,
573 personal groups are used for ACL delegation and adding to ticket roles
574 PrincipalId defaults to the current user's principal id.
576 Returns a tuple of (Id, Message). If id is 0, the create failed
580 sub CreatePersonalGroup {
584 Description => undef,
585 PrincipalId => $self->CurrentUser->PrincipalId,
589 if ( $self->CurrentUser->PrincipalId == $args{'PrincipalId'} ) {
591 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups') ) {
592 $RT::Logger->warning( $self->CurrentUser->Name
593 . " Tried to create a group without permission." );
594 return ( 0, $self->loc('Permission Denied') );
599 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
600 $RT::Logger->warning( $self->CurrentUser->Name
601 . " Tried to create a group without permission." );
602 return ( 0, $self->loc('Permission Denied') );
609 Domain => 'Personal',
611 Instance => $args{'PrincipalId'},
612 Name => $args{'Name'},
613 Description => $args{'Description'}
620 # {{{ CreateRoleGroup
622 =head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
624 A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
625 Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
626 Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
627 Instance is the id of the ticket or queue in question
629 This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
631 Returns a tuple of (Id, Message). If id is 0, the create failed
635 sub CreateRoleGroup {
637 my %args = ( Instance => undef,
641 unless ( $args{'Type'} =~ /^(?:Cc|AdminCc|Requestor|Owner)$/ ) {
642 return ( 0, $self->loc("Invalid Group Type") );
646 return ( $self->_Create( Domain => $args{'Domain'},
647 Instance => $args{'Instance'},
648 Type => $args{'Type'},
649 InsideTransaction => 1 ) );
665 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
666 return ( 0, 'Permission Denied' );
669 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
672 # Remove the principal object
673 # Remove this group from anything it's a member of.
674 # Remove all cached members of this group
675 # Remove any rights granted to this group
676 # remove any rights delegated by way of this group
678 return ( $self->SUPER::Delete(@_) );
683 =head2 SetDisabled BOOL
685 If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
686 It will not appear in most group listings.
688 This routine finds all the cached group members that are members of this group (recursively) and disables them.
696 if ($self->Domain eq 'Personal') {
697 if ($self->CurrentUser->PrincipalId == $self->Instance) {
698 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
699 return ( 0, $self->loc('Permission Denied') );
702 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
703 return ( 0, $self->loc('Permission Denied') );
708 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
709 return (0, $self->loc('Permission Denied'));
712 $RT::Handle->BeginTransaction();
713 $self->PrincipalObj->SetDisabled($val);
718 # Find all occurrences of this member as a member of this group
719 # in the cache and nuke them, recursively.
721 # The following code will delete all Cached Group members
722 # where this member's group is _not_ the primary group
723 # (Ie if we're deleting C as a member of B, and B happens to be
724 # a member of A, will delete C as a member of A without touching
727 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
729 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
731 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
732 # TODO what about the groups key cache?
733 RT::Principal->_InvalidateACLCache();
737 while ( my $item = $cached_submembers->Next() ) {
738 my $del_err = $item->SetDisabled($val);
740 $RT::Handle->Rollback();
741 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
746 $RT::Handle->Commit();
747 return (1, $self->loc("Succeeded"));
757 $self->PrincipalObj->Disabled(@_);
763 =head2 DeepMembersObj
765 Returns an RT::CachedGroupMembers object of this group's members.
771 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
773 #If we don't have rights, don't include any results
774 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
775 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
777 return ( $members_obj );
785 =head2 UserMembersObj
787 Returns an RT::Users object of this group's members, including
788 all members of subgroups
795 my $users = RT::Users->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?
800 my $cached_members = $users->NewAlias('CachedGroupMembers');
801 $users->Join(ALIAS1 => $cached_members, FIELD1 => 'MemberId',
802 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id');
803 $users->Limit(ALIAS => $cached_members,
806 VALUE => $self->PrincipalId);
818 Returns an RT::CachedGroupMembers object of this group's members.
824 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
826 #If we don't have rights, don't include any results
827 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
828 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
830 return ( $members_obj );
836 # {{{ MemberEmailAddresses
838 =head2 MemberEmailAddresses
840 Returns an array of the email addresses of all of this group's members
845 sub MemberEmailAddresses {
849 my $members = $self->UserMembersObj();
850 while (my $member = $members->Next) {
851 $addresses{$member->EmailAddress} = 1;
853 return(sort keys %addresses);
858 # {{{ MemberEmailAddressesAsString
860 =head2 MemberEmailAddressesAsString
862 Returns a comma delimited string of the email addresses of all users
863 who are members of this group.
868 sub MemberEmailAddressesAsString {
870 return (join(', ', $self->MemberEmailAddresses));
877 =head2 AddMember PRINCIPAL_ID
879 AddMember adds a principal to this group. It takes a single principal id.
880 Returns a two value array. the first value is true on successful
881 addition or 0 on failure. The second value is a textual status msg.
887 my $new_member = shift;
891 if ($self->Domain eq 'Personal') {
892 if ($self->CurrentUser->PrincipalId == $self->Instance) {
893 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
894 return ( 0, $self->loc('Permission Denied') );
897 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
898 return ( 0, $self->loc('Permission Denied') );
904 # We should only allow membership changes if the user has the right
905 # to modify group membership or the user is the principal in question
906 # and the user has the right to modify his own membership
907 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
908 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
909 $self->CurrentUserHasRight('AdminGroupMembership') ) {
910 #User has no permission to be doing this
911 return ( 0, $self->loc("Permission Denied") );
915 $self->_AddMember(PrincipalId => $new_member);
918 # A helper subroutine for AddMember that bypasses the ACL checks
919 # this should _ONLY_ ever be called from Ticket/Queue AddWatcher
920 # when we want to deal with groups according to queue rights
921 # In the dim future, this will all get factored out and life
924 # takes a paramhash of { PrincipalId => undef, InsideTransaction }
928 my %args = ( PrincipalId => undef,
929 InsideTransaction => undef,
931 my $new_member = $args{'PrincipalId'};
934 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
935 return(0, $self->loc("Group not found"));
938 unless ($new_member =~ /^\d+$/) {
939 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
943 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
944 $new_member_obj->Load($new_member);
947 unless ( $new_member_obj->Id ) {
948 $RT::Logger->debug("Couldn't find that principal");
949 return ( 0, $self->loc("Couldn't find that principal") );
952 if ( $self->HasMember( $new_member_obj ) ) {
954 #User is already a member of this group. no need to add it
955 return ( 0, $self->loc("Group already has member") );
957 if ( $new_member_obj->IsGroup &&
958 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
960 #This group can't be made to be a member of itself
961 return ( 0, $self->loc("Groups can't be members of their members"));
965 my $member_object = RT::GroupMember->new( $self->CurrentUser );
966 my $id = $member_object->Create(
967 Member => $new_member_obj,
968 Group => $self->PrincipalObj,
969 InsideTransaction => $args{'InsideTransaction'}
972 return ( 1, $self->loc("Member added") );
975 return(0, $self->loc("Couldn't add member to group"));
982 =head2 HasMember RT::Principal
984 Takes an RT::Principal object returns a GroupMember Id if that user is a
985 member of this group.
986 Returns undef if the user isn't a member of the group or if the current
987 user doesn't have permission to find out. Arguably, it should differentiate
988 between ACL failure and non membership.
994 my $principal = shift;
997 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
998 $RT::Logger->crit("Group::HasMember was called with an argument that".
999 "isn't an RT::Principal. It's $principal");
1003 unless ($principal->Id) {
1007 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1008 $member_obj->LoadByCols( MemberId => $principal->id,
1009 GroupId => $self->PrincipalId );
1011 #If we have a member object
1012 if ( defined $member_obj->id ) {
1013 return ( $member_obj->id );
1016 #If Load returns no objects, we have an undef id.
1018 #$RT::Logger->debug($self." does not contain principal ".$principal->id);
1025 # {{{ HasMemberRecursively
1027 =head2 HasMemberRecursively RT::Principal
1029 Takes an RT::Principal object and returns true if that user is a member of
1031 Returns undef if the user isn't a member of the group or if the current
1032 user doesn't have permission to find out. Arguably, it should differentiate
1033 between ACL failure and non membership.
1037 sub HasMemberRecursively {
1039 my $principal = shift;
1041 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
1042 $RT::Logger->crit("Group::HasMemberRecursively was called with an argument that".
1043 "isn't an RT::Principal. It's $principal");
1046 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1047 $member_obj->LoadByCols( MemberId => $principal->Id,
1048 GroupId => $self->PrincipalId ,
1052 #If we have a member object
1053 if ( defined $member_obj->id ) {
1057 #If Load returns no objects, we have an undef id.
1067 =head2 DeleteMember PRINCIPAL_ID
1069 Takes the principal id of a current user or group.
1070 If the current user has apropriate rights,
1071 removes that GroupMember from this group.
1072 Returns a two value array. the first value is true on successful
1073 addition or 0 on failure. The second value is a textual status msg.
1079 my $member_id = shift;
1082 # We should only allow membership changes if the user has the right
1083 # to modify group membership or the user is the principal in question
1084 # and the user has the right to modify his own membership
1086 if ($self->Domain eq 'Personal') {
1087 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1088 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1089 return ( 0, $self->loc('Permission Denied') );
1092 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1093 return ( 0, $self->loc('Permission Denied') );
1098 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
1099 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1100 $self->CurrentUserHasRight('AdminGroupMembership') ) {
1101 #User has no permission to be doing this
1102 return ( 0, $self->loc("Permission Denied") );
1105 $self->_DeleteMember($member_id);
1108 # A helper subroutine for DeleteMember that bypasses the ACL checks
1109 # this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1110 # when we want to deal with groups according to queue rights
1111 # In the dim future, this will all get factored out and life
1116 my $member_id = shift;
1118 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1120 $member_obj->LoadByCols( MemberId => $member_id,
1121 GroupId => $self->PrincipalId);
1124 #If we couldn't load it, return undef.
1125 unless ( $member_obj->Id() ) {
1126 $RT::Logger->debug("Group has no member with that id");
1127 return ( 0,$self->loc( "Group has no such member" ));
1130 #Now that we've checked ACLs and sanity, delete the groupmember
1131 my $val = $member_obj->Delete();
1134 return ( $val, $self->loc("Member deleted") );
1137 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1138 return ( 0, $self->loc("Member not deleted" ));
1144 # {{{ ACL Related routines
1150 if ($self->Domain eq 'Personal') {
1151 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1152 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1153 return ( 0, $self->loc('Permission Denied') );
1156 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1157 return ( 0, $self->loc('Permission Denied') );
1162 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
1163 return ( 0, $self->loc('Permission Denied') );
1166 return ( $self->SUPER::_Set(@_) );
1174 =item CurrentUserHasRight RIGHTNAME
1176 Returns true if the current user has the specified right for this group.
1179 TODO: we don't deal with membership visibility yet
1184 sub CurrentUserHasRight {
1191 $self->CurrentUser->HasRight( Object => $self,
1192 Right => $right )) {
1195 elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
1208 # {{{ Principal related routines
1212 Returns the principal object for this user. returns an empty RT::Principal
1213 if there's no principal object matching this user.
1214 The response is cached. PrincipalObj should never ever change.
1218 ok(my $u = RT::Group->new($RT::SystemUser));
1219 ok($u->Load(4), "Loaded the first user");
1220 ok($u->PrincipalObj->ObjectId == 4, "user 4 is the fourth principal");
1221 ok($u->PrincipalObj->PrincipalType eq 'Group' , "Principal 4 is a group");
1230 unless ($self->{'PrincipalObj'} &&
1231 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1232 ($self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
1234 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1235 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1236 'PrincipalType' => 'Group') ;
1238 return($self->{'PrincipalObj'});
1244 Returns this user's PrincipalId