3 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
5 # (Except where explictly superceded by other copyright notices)
7 # This work is made available to you under the terms of Version 2 of
8 # the GNU General Public License. A copy of that license should have
9 # been provided with this software, but in any event can be snarfed
12 # This work is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # Unless otherwise specified, all modifications, corrections or
18 # extensions to this work which alter its source code become the
19 # property of Best Practical Solutions, LLC when submitted for
20 # inclusion in the work.
24 # Released under the terms of version 2 of the GNU Public License
28 RT::Group - RT\'s group object
33 my $group = new RT::Group($CurrentUser);
41 Jesse Vincent, jesse@bestpractical.com
53 ok (require RT::Group);
55 ok (my $group = RT::Group->new($RT::SystemUser), "instantiated a group object");
56 ok (my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'TestGroup', Description => 'A test group',
57 ), 'Created a new group');
58 ok ($id != 0, "Group id is $id");
59 ok ($group->Name eq 'TestGroup', "The group's name is 'TestGroup'");
60 my $ng = RT::Group->new($RT::SystemUser);
62 ok($ng->LoadUserDefinedGroup('TestGroup'), "Loaded testgroup");
63 ok(($ng->id == $group->id), "Loaded the right group");
66 ok (($id,$msg) = $ng->AddMember('1'), "Added a member to the group");
68 ok (($id,$msg) = $ng->AddMember('2' ), "Added a member to the group");
70 ok (($id,$msg) = $ng->AddMember('3' ), "Added a member to the group");
73 # Group 1 now has members 1, 2 ,3
75 my $group_2 = RT::Group->new($RT::SystemUser);
76 ok (my ($id_2, $msg_2) = $group_2->CreateUserDefinedGroup( Name => 'TestGroup2', Description => 'A second test group'), , 'Created a new group');
77 ok ($id_2 != 0, "Created group 2 ok- $msg_2 ");
78 ok (($id,$msg) = $group_2->AddMember($ng->PrincipalId), "Made TestGroup a member of testgroup2");
80 ok (($id,$msg) = $group_2->AddMember('1' ), "Added member RT_System to the group TestGroup2");
83 # Group 2 how has 1, g1->{1, 2,3}
85 my $group_3 = RT::Group->new($RT::SystemUser);
86 ok (($id_3, $msg) = $group_3->CreateUserDefinedGroup( Name => 'TestGroup3', Description => 'A second test group'), 'Created a new group');
87 ok ($id_3 != 0, "Created group 3 ok - $msg");
88 ok (($id,$msg) =$group_3->AddMember($group_2->PrincipalId), "Made TestGroup a member of testgroup2");
91 # g3 now has g2->{1, g1->{1,2,3}}
93 my $principal_1 = RT::Principal->new($RT::SystemUser);
94 $principal_1->Load('1');
96 my $principal_2 = RT::Principal->new($RT::SystemUser);
97 $principal_2->Load('2');
99 ok (($id,$msg) = $group_3->AddMember('1' ), "Added member RT_System to the group TestGroup2");
102 # g3 now has 1, g2->{1, g1->{1,2,3}}
104 ok($group_3->HasMember($principal_2) == undef, "group 3 doesn't have member 2");
105 ok($group_3->HasMemberRecursively($principal_2), "group 3 has member 2 recursively");
106 ok($ng->HasMember($principal_2) , "group ".$ng->Id." has member 2");
107 my ($delid , $delmsg) =$ng->DeleteMember($principal_2->Id);
108 ok ($delid !=0, "Sucessfully deleted it-".$delid."-".$delmsg);
110 #Gotta reload the group objects, since we've been messing with various internals.
111 # we shouldn't need to do this.
112 #$ng->LoadUserDefinedGroup('TestGroup');
113 #$group_2->LoadUserDefinedGroup('TestGroup2');
114 #$group_3->LoadUserDefinedGroup('TestGroup');
117 # Group 2 how has 1, g1->{1, 3}
118 # g3 now has 1, g2->{1, g1->{1, 3}}
120 ok(!$ng->HasMember($principal_2) , "group ".$ng->Id." no longer has member 2");
121 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 doesn't have member 2");
122 ok($group_2->HasMemberRecursively($principal_2) == undef, "group 2 doesn't have member 2");
123 ok($ng->HasMember($principal_2) == undef, "group 1 doesn't have member 2");;
124 ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 has member 2 recursively");
135 no warnings qw(redefine);
138 use RT::GroupMembers;
142 use vars qw/$RIGHTS/;
145 AdminGroup => 'Modify group metadata or delete group', # loc_pair
146 AdminGroupMembership =>
147 'Modify membership roster for this group', # loc_pair
148 ModifyOwnMembership => 'Join or leave this group' # loc_pair
151 # Tell RT::ACE that this sort of object can get acls granted
152 $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
157 # TODO: This should be refactored out into an RT::ACLedObject or something
158 # stuff the rights into a hash of rights that can exist.
160 foreach my $right ( keys %{$RIGHTS} ) {
161 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
165 =head2 AvailableRights
167 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
171 sub AvailableRights {
177 # {{{ sub SelfDescription
179 =head2 SelfDescription
181 Returns a user-readable description of what this group is for and what it's named.
185 sub SelfDescription {
187 if ($self->Domain eq 'ACLEquivalence') {
188 my $user = RT::Principal->new($self->CurrentUser);
189 $user->Load($self->Instance);
190 return $self->loc("user [_1]",$user->Object->Name);
192 elsif ($self->Domain eq 'UserDefined') {
193 return $self->loc("group '[_1]'",$self->Name);
195 elsif ($self->Domain eq 'Personal') {
196 my $user = RT::User->new($self->CurrentUser);
197 $user->Load($self->Instance);
198 return $self->loc("personal group '[_1]' for user '[_2]'",$self->Name, $user->Name);
200 elsif ($self->Domain eq 'RT::System-Role') {
201 return $self->loc("system [_1]",$self->Type);
203 elsif ($self->Domain eq 'RT::Queue-Role') {
204 my $queue = RT::Queue->new($self->CurrentUser);
205 $queue->Load($self->Instance);
206 return $self->loc("queue [_1] [_2]",$queue->Name, $self->Type);
208 elsif ($self->Domain eq 'RT::Ticket-Role') {
209 return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Type);
211 elsif ($self->Domain eq 'SystemInternal') {
212 return $self->loc("system group '[_1]'",$self->Type);
215 return $self->loc("undescribed group [_1]",$self->Id);
225 Load a group object from the database. Takes a single argument.
226 If the argument is numerical, load by the column 'id'. Otherwise,
233 my $identifier = shift || return undef;
235 #if it's an int, load by id. otherwise, load by name.
236 if ( $identifier !~ /\D/ ) {
237 $self->SUPER::LoadById($identifier);
240 $RT::Logger->crit("Group -> Load called with a bogus argument");
247 # {{{ sub LoadUserDefinedGroup
249 =head2 LoadUserDefinedGroup NAME
251 Loads a system group from the database. The only argument is
257 sub LoadUserDefinedGroup {
259 my $identifier = shift;
261 $self->LoadByCols( "Domain" => 'UserDefined',
262 "Name" => $identifier );
267 # {{{ sub LoadACLEquivalenceGroup
269 =head2 LoadACLEquivalenceGroup PRINCIPAL
271 Loads a user's acl equivalence group. Takes a principal object.
272 ACL equivalnce groups are used to simplify the acl system. Each user
273 has one group that only he is a member of. Rights granted to the user
274 are actually granted to that group. This greatly simplifies ACL checks.
275 While this results in a somewhat more complex setup when creating users
276 and granting ACLs, it _greatly_ simplifies acl checks.
282 sub LoadACLEquivalenceGroup {
286 $self->LoadByCols( "Domain" => 'ACLEquivalence',
287 "Type" => 'UserEquiv',
288 "Instance" => $princ->Id);
293 # {{{ sub LoadPersonalGroup
295 =head2 LoadPersonalGroup {Name => NAME, User => USERID}
297 Loads a personal group from the database.
301 sub LoadPersonalGroup {
303 my %args = ( Name => undef,
307 $self->LoadByCols( "Domain" => 'Personal',
308 "Instance" => $args{'User'},
310 "Name" => $args{'Name'} );
315 # {{{ sub LoadSystemInternalGroup
317 =head2 LoadSystemInternalGroup NAME
319 Loads a Pseudo group from the database. The only argument is
325 sub LoadSystemInternalGroup {
327 my $identifier = shift;
329 $self->LoadByCols( "Domain" => 'SystemInternal',
332 "Type" => $identifier );
337 # {{{ sub LoadTicketRoleGroup
339 =head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Type => TYPE }
341 Loads a ticket group from the database.
343 Takes a param hash with 2 parameters:
345 Ticket is the TicketId we're curious about
346 Type is the type of Group we're trying to load:
347 Requestor, Cc, AdminCc, Owner
351 sub LoadTicketRoleGroup {
353 my %args = (Ticket => undef,
356 $self->LoadByCols( Domain => 'RT::Ticket-Role',
357 Instance =>$args{'Ticket'},
358 Type => $args{'Type'}
364 # {{{ sub LoadQueueRoleGroup
366 =head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
368 Loads a Queue group from the database.
370 Takes a param hash with 2 parameters:
372 Queue is the QueueId we're curious about
373 Type is the type of Group we're trying to load:
374 Requestor, Cc, AdminCc, Owner
378 sub LoadQueueRoleGroup {
380 my %args = (Queue => undef,
383 $self->LoadByCols( Domain => 'RT::Queue-Role',
384 Instance =>$args{'Queue'},
385 Type => $args{'Type'}
391 # {{{ sub LoadSystemRoleGroup
393 =head2 LoadSystemRoleGroup Type
395 Loads a System group from the database.
397 Takes a single param: Type
399 Type is the type of Group we're trying to load:
400 Requestor, Cc, AdminCc, Owner
404 sub LoadSystemRoleGroup {
407 $self->LoadByCols( Domain => 'RT::System-Role',
417 You need to specify what sort of group you're creating by calling one of the other
418 Create_____ routines.
424 $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
425 return(0,$self->loc('Permission Denied'));
434 Takes a paramhash with named arguments: Name, Description.
436 Returns a tuple of (Id, Message). If id is 0, the create failed
444 Description => undef,
448 InsideTransaction => undef,
452 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
453 # Groups deal with principal ids, rather than user ids.
454 # When creating this group, set up a principal Id for it.
455 my $principal = RT::Principal->new( $self->CurrentUser );
456 my $principal_id = $principal->Create(
457 PrincipalType => 'Group',
460 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
463 $self->SUPER::Create(
465 Name => $args{'Name'},
466 Description => $args{'Description'},
467 Type => $args{'Type'},
468 Domain => $args{'Domain'},
469 Instance => $args{'Instance'}
473 return ( 0, $self->loc('Could not create group') );
476 # If we couldn't create a principal Id, get the fuck out.
477 unless ($principal_id) {
478 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
479 $self->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
480 return ( 0, $self->loc('Could not create group') );
483 # Now we make the group a member of itself as a cached group member
484 # this needs to exist so that group ACL checks don't fall over.
485 # you're checking CachedGroupMembers to see if the principal in question
486 # is a member of the principal the rights have been granted too
488 # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
489 # cached members. thankfully, we're creating the group now...so it has no members.
490 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
491 $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
495 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
496 return ( $id, $self->loc("Group created") );
501 # {{{ CreateUserDefinedGroup
503 =head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
505 A helper subroutine which creates a system group
507 Returns a tuple of (Id, Message). If id is 0, the create failed
511 sub CreateUserDefinedGroup {
514 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
515 $RT::Logger->warning( $self->CurrentUser->Name
516 . " Tried to create a group without permission." );
517 return ( 0, $self->loc('Permission Denied') );
520 return($self->_Create( Domain => 'UserDefined', Type => '', Instance => '', @_));
525 # {{{ _CreateACLEquivalenceGroup
527 =head2 _CreateACLEquivalenceGroup { Principal }
529 A helper subroutine which creates a group containing only
530 an individual user. This gets used by the ACL system to check rights.
531 Yes, it denormalizes the data, but that's ok, as we totally win on performance.
533 Returns a tuple of (Id, Message). If id is 0, the create failed
537 sub _CreateACLEquivalenceGroup {
541 my $id = $self->_Create( Domain => 'ACLEquivalence',
543 Name => 'User '. $princ->Object->Id,
544 Description => 'ACL equiv. for user '.$princ->Object->Id,
545 Instance => $princ->Id,
546 InsideTransaction => 1);
548 $RT::Logger->crit("Couldn't create ACL equivalence group");
552 # We use stashuser so we don't get transactions inside transactions
553 # and so we bypass all sorts of cruft we don't need
554 my $aclstash = RT::GroupMember->new($self->CurrentUser);
555 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
559 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
560 # We call super delete so we don't get acl checked.
561 $self->SUPER::Delete();
569 # {{{ CreatePersonalGroup
571 =head2 CreatePersonalGroup { PrincipalId => PRINCIPAL_ID, Name => "name", Description => "Description"}
573 A helper subroutine which creates a personal group. Generally,
574 personal groups are used for ACL delegation and adding to ticket roles
575 PrincipalId defaults to the current user's principal id.
577 Returns a tuple of (Id, Message). If id is 0, the create failed
581 sub CreatePersonalGroup {
585 Description => undef,
586 PrincipalId => $self->CurrentUser->PrincipalId,
590 if ( $self->CurrentUser->PrincipalId == $args{'PrincipalId'} ) {
592 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups') ) {
593 $RT::Logger->warning( $self->CurrentUser->Name
594 . " Tried to create a group without permission." );
595 return ( 0, $self->loc('Permission Denied') );
600 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
601 $RT::Logger->warning( $self->CurrentUser->Name
602 . " Tried to create a group without permission." );
603 return ( 0, $self->loc('Permission Denied') );
610 Domain => 'Personal',
612 Instance => $args{'PrincipalId'},
613 Name => $args{'Name'},
614 Description => $args{'Description'}
621 # {{{ CreateRoleGroup
623 =head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
625 A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
626 Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
627 Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
628 Instance is the id of the ticket or queue in question
630 This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
632 Returns a tuple of (Id, Message). If id is 0, the create failed
636 sub CreateRoleGroup {
638 my %args = ( Instance => undef,
642 unless ( $args{'Type'} =~ /^(?:Cc|AdminCc|Requestor|Owner)$/ ) {
643 return ( 0, $self->loc("Invalid Group Type") );
647 return ( $self->_Create( Domain => $args{'Domain'},
648 Instance => $args{'Instance'},
649 Type => $args{'Type'},
650 InsideTransaction => 1 ) );
666 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
667 return ( 0, 'Permission Denied' );
670 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
673 # Remove the principal object
674 # Remove this group from anything it's a member of.
675 # Remove all cached members of this group
676 # Remove any rights granted to this group
677 # remove any rights delegated by way of this group
679 return ( $self->SUPER::Delete(@_) );
684 =head2 SetDisabled BOOL
686 If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
687 It will not appear in most group listings.
689 This routine finds all the cached group members that are members of this group (recursively) and disables them.
697 if ($self->Domain eq 'Personal') {
698 if ($self->CurrentUser->PrincipalId == $self->Instance) {
699 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
700 return ( 0, $self->loc('Permission Denied') );
703 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
704 return ( 0, $self->loc('Permission Denied') );
709 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
710 return (0, $self->loc('Permission Denied'));
713 $RT::Handle->BeginTransaction();
714 $self->PrincipalObj->SetDisabled($val);
719 # Find all occurrences of this member as a member of this group
720 # in the cache and nuke them, recursively.
722 # The following code will delete all Cached Group members
723 # where this member's group is _not_ the primary group
724 # (Ie if we're deleting C as a member of B, and B happens to be
725 # a member of A, will delete C as a member of A without touching
728 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
730 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
732 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
733 # TODO what about the groups key cache?
734 RT::Principal->_InvalidateACLCache();
738 while ( my $item = $cached_submembers->Next() ) {
739 my $del_err = $item->SetDisabled($val);
741 $RT::Handle->Rollback();
742 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
747 $RT::Handle->Commit();
748 return (1, $self->loc("Succeeded"));
758 $self->PrincipalObj->Disabled(@_);
764 =head2 DeepMembersObj
766 Returns an RT::CachedGroupMembers object of this group's members.
772 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
774 #If we don't have rights, don't include any results
775 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
776 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
778 return ( $members_obj );
786 =head2 UserMembersObj
788 Returns an RT::Users object of this group's members, including
789 all members of subgroups
796 my $users = RT::Users->new($self->CurrentUser);
798 #If we don't have rights, don't include any results
799 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
801 my $principals = $users->NewAlias('Principals');
803 $users->Join(ALIAS1 => 'main', FIELD1 => 'id',
804 ALIAS2 => $principals, FIELD2 => 'ObjectId');
805 $users->Limit(ALIAS =>$principals,
806 FIELD => 'PrincipalType', OPERATOR => '=', VALUE => 'User');
808 my $cached_members = $users->NewAlias('CachedGroupMembers');
809 $users->Join(ALIAS1 => $cached_members, FIELD1 => 'MemberId',
810 ALIAS2 => $principals, FIELD2 => 'id');
811 $users->Limit(ALIAS => $cached_members,
814 VALUE => $self->PrincipalId);
827 Returns an RT::CachedGroupMembers object of this group's members.
833 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
835 #If we don't have rights, don't include any results
836 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
837 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
839 return ( $members_obj );
845 # {{{ MemberEmailAddresses
847 =head2 MemberEmailAddresses
849 Returns an array of the email addresses of all of this group's members
854 sub MemberEmailAddresses {
858 my $members = $self->UserMembersObj();
859 while (my $member = $members->Next) {
860 $addresses{$member->EmailAddress} = 1;
862 return(sort keys %addresses);
867 # {{{ MemberEmailAddressesAsString
869 =head2 MemberEmailAddressesAsString
871 Returns a comma delimited string of the email addresses of all users
872 who are members of this group.
877 sub MemberEmailAddressesAsString {
879 return (join(', ', $self->MemberEmailAddresses));
886 =head2 AddMember PRINCIPAL_ID
888 AddMember adds a principal to this group. It takes a single principal id.
889 Returns a two value array. the first value is true on successful
890 addition or 0 on failure. The second value is a textual status msg.
896 my $new_member = shift;
900 if ($self->Domain eq 'Personal') {
901 if ($self->CurrentUser->PrincipalId == $self->Instance) {
902 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
903 return ( 0, $self->loc('Permission Denied') );
906 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
907 return ( 0, $self->loc('Permission Denied') );
913 # We should only allow membership changes if the user has the right
914 # to modify group membership or the user is the principal in question
915 # and the user has the right to modify his own membership
916 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
917 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
918 $self->CurrentUserHasRight('AdminGroupMembership') ) {
919 #User has no permission to be doing this
920 return ( 0, $self->loc("Permission Denied") );
924 $self->_AddMember(PrincipalId => $new_member);
927 # A helper subroutine for AddMember that bypasses the ACL checks
928 # this should _ONLY_ ever be called from Ticket/Queue AddWatcher
929 # when we want to deal with groups according to queue rights
930 # In the dim future, this will all get factored out and life
933 # takes a paramhash of { PrincipalId => undef, InsideTransaction }
937 my %args = ( PrincipalId => undef,
938 InsideTransaction => undef,
940 my $new_member = $args{'PrincipalId'};
943 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
944 return(0, $self->loc("Group not found"));
947 unless ($new_member =~ /^\d+$/) {
948 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
952 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
953 $new_member_obj->Load($new_member);
956 unless ( $new_member_obj->Id ) {
957 $RT::Logger->debug("Couldn't find that principal");
958 return ( 0, $self->loc("Couldn't find that principal") );
961 if ( $self->HasMember( $new_member_obj ) ) {
963 #User is already a member of this group. no need to add it
964 return ( 0, $self->loc("Group already has member") );
966 if ( $new_member_obj->IsGroup &&
967 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
969 #This group can't be made to be a member of itself
970 return ( 0, $self->loc("Groups can't be members of their members"));
974 my $member_object = RT::GroupMember->new( $self->CurrentUser );
975 my $id = $member_object->Create(
976 Member => $new_member_obj,
977 Group => $self->PrincipalObj,
978 InsideTransaction => $args{'InsideTransaction'}
981 return ( 1, $self->loc("Member added") );
984 return(0, $self->loc("Couldn't add member to group"));
991 =head2 HasMember RT::Principal
993 Takes an RT::Principal object returns a GroupMember Id if that user is a
994 member of this group.
995 Returns undef if the user isn't a member of the group or if the current
996 user doesn't have permission to find out. Arguably, it should differentiate
997 between ACL failure and non membership.
1003 my $principal = shift;
1006 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
1007 $RT::Logger->crit("Group::HasMember was called with an argument that".
1008 "isn't an RT::Principal. It's $principal");
1012 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1013 $member_obj->LoadByCols( MemberId => $principal->id,
1014 GroupId => $self->PrincipalId );
1016 #If we have a member object
1017 if ( defined $member_obj->id ) {
1018 return ( $member_obj->id );
1021 #If Load returns no objects, we have an undef id.
1023 #$RT::Logger->debug($self." does not contain principal ".$principal->id);
1030 # {{{ HasMemberRecursively
1032 =head2 HasMemberRecursively RT::Principal
1034 Takes an RT::Principal object and returns true if that user is a member of
1036 Returns undef if the user isn't a member of the group or if the current
1037 user doesn't have permission to find out. Arguably, it should differentiate
1038 between ACL failure and non membership.
1042 sub HasMemberRecursively {
1044 my $principal = shift;
1046 unless (UNIVERSAL::isa($principal,'RT::Principal')) {
1047 $RT::Logger->crit("Group::HasMemberRecursively was called with an argument that".
1048 "isn't an RT::Principal. It's $principal");
1051 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1052 $member_obj->LoadByCols( MemberId => $principal->Id,
1053 GroupId => $self->PrincipalId ,
1057 #If we have a member object
1058 if ( defined $member_obj->id ) {
1062 #If Load returns no objects, we have an undef id.
1072 =head2 DeleteMember PRINCIPAL_ID
1074 Takes the principal id of a current user or group.
1075 If the current user has apropriate rights,
1076 removes that GroupMember from this group.
1077 Returns a two value array. the first value is true on successful
1078 addition or 0 on failure. The second value is a textual status msg.
1084 my $member_id = shift;
1087 # We should only allow membership changes if the user has the right
1088 # to modify group membership or the user is the principal in question
1089 # and the user has the right to modify his own membership
1091 if ($self->Domain eq 'Personal') {
1092 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1093 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1094 return ( 0, $self->loc('Permission Denied') );
1097 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1098 return ( 0, $self->loc('Permission Denied') );
1103 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
1104 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1105 $self->CurrentUserHasRight('AdminGroupMembership') ) {
1106 #User has no permission to be doing this
1107 return ( 0, $self->loc("Permission Denied") );
1110 $self->_DeleteMember($member_id);
1113 # A helper subroutine for DeleteMember that bypasses the ACL checks
1114 # this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1115 # when we want to deal with groups according to queue rights
1116 # In the dim future, this will all get factored out and life
1121 my $member_id = shift;
1123 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1125 $member_obj->LoadByCols( MemberId => $member_id,
1126 GroupId => $self->PrincipalId);
1129 #If we couldn't load it, return undef.
1130 unless ( $member_obj->Id() ) {
1131 $RT::Logger->debug("Group has no member with that id");
1132 return ( 0,$self->loc( "Group has no such member" ));
1135 #Now that we've checked ACLs and sanity, delete the groupmember
1136 my $val = $member_obj->Delete();
1139 return ( $val, $self->loc("Member deleted") );
1142 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1143 return ( 0, $self->loc("Member not deleted" ));
1149 # {{{ ACL Related routines
1155 if ($self->Domain eq 'Personal') {
1156 if ($self->CurrentUser->PrincipalId == $self->Instance) {
1157 unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
1158 return ( 0, $self->loc('Permission Denied') );
1161 unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
1162 return ( 0, $self->loc('Permission Denied') );
1167 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
1168 return ( 0, $self->loc('Permission Denied') );
1171 return ( $self->SUPER::_Set(@_) );
1179 =item CurrentUserHasRight RIGHTNAME
1181 Returns true if the current user has the specified right for this group.
1184 TODO: we don't deal with membership visibility yet
1189 sub CurrentUserHasRight {
1196 $self->CurrentUser->HasRight( Object => $self,
1197 Right => $right )) {
1200 elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
1213 # {{{ Principal related routines
1217 Returns the principal object for this user. returns an empty RT::Principal
1218 if there's no principal object matching this user.
1219 The response is cached. PrincipalObj should never ever change.
1223 ok(my $u = RT::Group->new($RT::SystemUser));
1224 ok($u->Load(4), "Loaded the first user");
1225 ok($u->PrincipalObj->ObjectId == 4, "user 4 is the fourth principal");
1226 ok($u->PrincipalObj->PrincipalType eq 'Group' , "Principal 4 is a group");
1235 unless ($self->{'PrincipalObj'} &&
1236 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1237 ($self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
1239 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1240 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1241 'PrincipalType' => 'Group') ;
1243 return($self->{'PrincipalObj'});
1249 Returns this user's PrincipalId