2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
7 # <sales@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., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
31 # CONTRIBUTION SUBMISSION POLICY:
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
48 # END BPS TAGGED BLOCK }}}
50 # Released under the terms of version 2 of the GNU Public License
54 RT::Group - RT\'s group object
59 my $group = RT::Group->new($CurrentUser);
80 use base 'RT::Record';
91 use vars qw/$RIGHTS $RIGHT_CATEGORIES/;
94 AdminGroup => 'Modify group metadata or delete group', # loc_pair
95 AdminGroupMembership => 'Modify group membership roster', # loc_pair
96 ModifyOwnMembership => 'Join or leave group', # loc_pair
97 EditSavedSearches => 'Create, modify and delete saved searches', # loc_pair
98 ShowSavedSearches => 'View saved searches', # loc_pair
99 SeeGroup => 'View group', # loc_pair
100 SeeGroupDashboard => 'View group dashboards', # loc_pair
101 CreateGroupDashboard => 'Create group dashboards', # loc_pair
102 ModifyGroupDashboard => 'Modify group dashboards', # loc_pair
103 DeleteGroupDashboard => 'Delete group dashboards', # loc_pair
106 $RIGHT_CATEGORIES = {
107 AdminGroup => 'Admin',
108 AdminGroupMembership => 'Admin',
109 ModifyOwnMembership => 'Staff',
110 EditSavedSearches => 'Admin',
111 ShowSavedSearches => 'Staff',
113 SeeGroupDashboard => 'Staff',
114 CreateGroupDashboard => 'Admin',
115 ModifyGroupDashboard => 'Admin',
116 DeleteGroupDashboard => 'Admin',
119 # Tell RT::ACE that this sort of object can get acls granted
120 $RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
125 # TODO: This should be refactored out into an RT::ACLedObject or something
126 # stuff the rights into a hash of rights that can exist.
128 __PACKAGE__->AddRights(%$RIGHTS);
129 __PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
131 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
133 Adds the given rights to the list of possible rights. This method
134 should be called during server startup, not at runtime.
141 $RIGHTS = { %$RIGHTS, %new };
142 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
143 map { lc($_) => $_ } keys %new);
146 =head2 AvailableRights
148 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
152 sub AvailableRights {
157 =head2 RightCategories
159 Returns a hashref where the keys are rights for this type of object and the
160 values are the category (General, Staff, Admin) the right falls into.
164 sub RightCategories {
165 return $RIGHT_CATEGORIES;
168 =head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
170 Adds the given right and category pairs to the list of right categories. This
171 method should be called during server startup, not at runtime.
175 sub AddRightCategories {
176 my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
178 $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
183 =head2 SelfDescription
185 Returns a user-readable description of what this group is for and what it's named.
189 sub SelfDescription {
191 if ($self->Domain eq 'ACLEquivalence') {
192 my $user = RT::Principal->new($self->CurrentUser);
193 $user->Load($self->Instance);
194 return $self->loc("user [_1]",$user->Object->Name);
196 elsif ($self->Domain eq 'UserDefined') {
197 return $self->loc("group '[_1]'",$self->Name);
199 elsif ($self->Domain eq 'RT::System-Role') {
200 return $self->loc("system [_1]",$self->Type);
202 elsif ($self->Domain eq 'RT::Queue-Role') {
203 my $queue = RT::Queue->new($self->CurrentUser);
204 $queue->Load($self->Instance);
205 return $self->loc("queue [_1] [_2]",$queue->Name, $self->Type);
207 elsif ($self->Domain eq 'RT::Ticket-Role') {
208 return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Type);
210 elsif ($self->Domain eq 'SystemInternal') {
211 return $self->loc("system group '[_1]'",$self->Type);
214 return $self->loc("undescribed group [_1]",$self->Id);
222 Load a group object from the database. Takes a single argument.
223 If the argument is numerical, load by the column 'id'. Otherwise,
230 my $identifier = shift || return undef;
232 if ( $identifier !~ /\D/ ) {
233 $self->SUPER::LoadById($identifier);
236 $RT::Logger->crit("Group -> Load called with a bogus argument");
243 =head2 LoadUserDefinedGroup NAME
245 Loads a system group from the database. The only argument is
251 sub LoadUserDefinedGroup {
253 my $identifier = shift;
255 if ( $identifier =~ /^\d+$/ ) {
256 return $self->LoadByCols(
257 Domain => 'UserDefined',
261 return $self->LoadByCols(
262 Domain => 'UserDefined',
270 =head2 LoadACLEquivalenceGroup PRINCIPAL
272 Loads a user's acl equivalence group. Takes a principal object or its ID.
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.
281 sub LoadACLEquivalenceGroup {
283 my $principal = shift;
284 $principal = $principal->id if ref $principal;
286 return $self->LoadByCols(
287 Domain => 'ACLEquivalence',
289 Instance => $principal,
296 =head2 LoadSystemInternalGroup NAME
298 Loads a Pseudo group from the database. The only argument is
304 sub LoadSystemInternalGroup {
306 my $identifier = shift;
308 return $self->LoadByCols(
309 Domain => 'SystemInternal',
316 =head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Type => TYPE }
318 Loads a ticket group from the database.
320 Takes a param hash with 2 parameters:
322 Ticket is the TicketId we're curious about
323 Type is the type of Group we're trying to load:
324 Requestor, Cc, AdminCc, Owner
328 sub LoadTicketRoleGroup {
330 my %args = (Ticket => '0',
333 $self->LoadByCols( Domain => 'RT::Ticket-Role',
334 Instance =>$args{'Ticket'},
335 Type => $args{'Type'}
341 =head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
343 Loads a Queue group from the database.
345 Takes a param hash with 2 parameters:
347 Queue is the QueueId we're curious about
348 Type is the type of Group we're trying to load:
349 Requestor, Cc, AdminCc, Owner
353 sub LoadQueueRoleGroup {
355 my %args = (Queue => undef,
358 $self->LoadByCols( Domain => 'RT::Queue-Role',
359 Instance =>$args{'Queue'},
360 Type => $args{'Type'}
366 =head2 LoadSystemRoleGroup Type
368 Loads a System group from the database.
370 Takes a single param: Type
372 Type is the type of Group we're trying to load:
373 Requestor, Cc, AdminCc, Owner
377 sub LoadSystemRoleGroup {
380 $self->LoadByCols( Domain => 'RT::System-Role',
389 You need to specify what sort of group you're creating by calling one of the other
390 Create_____ routines.
396 $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
397 return(0,$self->loc('Permission Denied'));
404 Takes a paramhash with named arguments: Name, Description.
406 Returns a tuple of (Id, Message). If id is 0, the create failed
414 Description => undef,
418 InsideTransaction => undef,
419 _RecordTransaction => 1,
423 # Enforce uniqueness on user defined group names
424 if ($args{'Domain'} and $args{'Domain'} eq 'UserDefined') {
425 my ($ok, $msg) = $self->_ValidateUserDefinedName($args{'Name'});
426 return ($ok, $msg) if not $ok;
429 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
430 # Groups deal with principal ids, rather than user ids.
431 # When creating this group, set up a principal Id for it.
432 my $principal = RT::Principal->new( $self->CurrentUser );
433 my $principal_id = $principal->Create(
434 PrincipalType => 'Group',
437 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
439 $self->SUPER::Create(
441 Name => $args{'Name'},
442 Description => $args{'Description'},
443 Type => $args{'Type'},
444 Domain => $args{'Domain'},
445 Instance => ($args{'Instance'} || '0')
449 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
450 return ( 0, $self->loc('Could not create group') );
453 # If we couldn't create a principal Id, get the fuck out.
454 unless ($principal_id) {
455 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
456 $RT::Logger->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
457 return ( 0, $self->loc('Could not create group') );
460 # Now we make the group a member of itself as a cached group member
461 # this needs to exist so that group ACL checks don't fall over.
462 # you're checking CachedGroupMembers to see if the principal in question
463 # is a member of the principal the rights have been granted too
465 # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
466 # cached members. thankfully, we're creating the group now...so it has no members.
467 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
468 $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
471 if ( $args{'_RecordTransaction'} ) {
472 $self->_NewTransaction( Type => "Create" );
475 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
477 return ( $id, $self->loc("Group created") );
482 =head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
484 A helper subroutine which creates a system group
486 Returns a tuple of (Id, Message). If id is 0, the create failed
490 sub CreateUserDefinedGroup {
493 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
494 $RT::Logger->warning( $self->CurrentUser->Name
495 . " Tried to create a group without permission." );
496 return ( 0, $self->loc('Permission Denied') );
499 return($self->_Create( Domain => 'UserDefined', Type => '', Instance => '', @_));
502 =head2 ValidateName VALUE
504 Enforces unique user defined group names when updating
509 my ($self, $value) = @_;
511 if ($self->Domain and $self->Domain eq 'UserDefined') {
512 my ($ok, $msg) = $self->_ValidateUserDefinedName($value);
513 # It's really too bad we can't pass along the actual error
516 return $self->SUPER::ValidateName($value);
519 =head2 _ValidateUserDefinedName VALUE
521 Returns true if the user defined group name isn't in use, false otherwise.
525 sub _ValidateUserDefinedName {
526 my ($self, $value) = @_;
528 return (0, 'Name is required') unless length $value;
530 my $dupcheck = RT::Group->new(RT->SystemUser);
531 $dupcheck->LoadUserDefinedGroup($value);
532 return (0, $self->loc("Group name '[_1]' is already in use", $value))
537 =head2 _CreateACLEquivalenceGroup { Principal }
539 A helper subroutine which creates a group containing only
540 an individual user. This gets used by the ACL system to check rights.
541 Yes, it denormalizes the data, but that's ok, as we totally win on performance.
543 Returns a tuple of (Id, Message). If id is 0, the create failed
547 sub _CreateACLEquivalenceGroup {
551 my $id = $self->_Create( Domain => 'ACLEquivalence',
553 Name => 'User '. $princ->Object->Id,
554 Description => 'ACL equiv. for user '.$princ->Object->Id,
555 Instance => $princ->Id,
556 InsideTransaction => 1,
557 _RecordTransaction => 0 );
559 $RT::Logger->crit("Couldn't create ACL equivalence group");
563 # We use stashuser so we don't get transactions inside transactions
564 # and so we bypass all sorts of cruft we don't need
565 my $aclstash = RT::GroupMember->new($self->CurrentUser);
566 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
570 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
571 # We call super delete so we don't get acl checked.
572 $self->SUPER::Delete();
581 =head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
583 A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
584 Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
585 Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
586 Instance is the id of the ticket or queue in question
588 This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
590 Returns a tuple of (Id, Message). If id is 0, the create failed
594 sub CreateRoleGroup {
596 my %args = ( Instance => undef,
601 unless (RT::Queue->IsRoleGroupType($args{Type})) {
602 return ( 0, $self->loc("Invalid Group Type") );
606 return ( $self->_Create( Domain => $args{'Domain'},
607 Instance => $args{'Instance'},
608 Type => $args{'Type'},
609 InsideTransaction => 1 ) );
623 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
624 return ( 0, 'Permission Denied' );
627 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
630 # Remove the principal object
631 # Remove this group from anything it's a member of.
632 # Remove all cached members of this group
633 # Remove any rights granted to this group
634 # remove any rights delegated by way of this group
636 return ( $self->SUPER::Delete(@_) );
640 =head2 SetDisabled BOOL
642 If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
643 It will not appear in most group listings.
645 This routine finds all the cached group members that are members of this group (recursively) and disables them.
654 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
655 return (0, $self->loc('Permission Denied'));
657 $RT::Handle->BeginTransaction();
658 $self->PrincipalObj->SetDisabled($val);
663 # Find all occurrences of this member as a member of this group
664 # in the cache and nuke them, recursively.
666 # The following code will delete all Cached Group members
667 # where this member's group is _not_ the primary group
668 # (Ie if we're deleting C as a member of B, and B happens to be
669 # a member of A, will delete C as a member of A without touching
672 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
674 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
676 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
677 # TODO what about the groups key cache?
678 RT::Principal->InvalidateACLCache();
682 while ( my $item = $cached_submembers->Next() ) {
683 my $del_err = $item->SetDisabled($val);
685 $RT::Handle->Rollback();
686 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
691 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
693 $RT::Handle->Commit();
695 return (1, $self->loc("Group disabled"));
697 return (1, $self->loc("Group enabled"));
707 $self->PrincipalObj->Disabled(@_);
712 =head2 DeepMembersObj
714 Returns an RT::CachedGroupMembers object of this group's members,
715 including all members of subgroups.
721 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
723 #If we don't have rights, don't include any results
724 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
725 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
727 return ( $members_obj );
735 Returns an RT::GroupMembers object of this group's direct members.
741 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
743 #If we don't have rights, don't include any results
744 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
745 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
747 return ( $members_obj );
753 =head2 GroupMembersObj [Recursively => 1]
755 Returns an L<RT::Groups> object of this group's members.
756 By default returns groups including all subgroups, but
757 could be changed with C<Recursively> named argument.
759 B<Note> that groups are not filtered by type and result
760 may contain as well system groups and others.
764 sub GroupMembersObj {
766 my %args = ( Recursively => 1, @_ );
768 my $groups = RT::Groups->new( $self->CurrentUser );
769 my $members_table = $args{'Recursively'}?
770 'CachedGroupMembers': 'GroupMembers';
772 my $members_alias = $groups->NewAlias( $members_table );
774 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
775 ALIAS2 => $groups->PrincipalsAlias, FIELD2 => 'id',
778 ALIAS => $members_alias,
780 VALUE => $self->PrincipalId,
783 ALIAS => $members_alias,
786 ) if $args{'Recursively'};
793 =head2 UserMembersObj
795 Returns an L<RT::Users> object of this group's members, by default
796 returns users including all members of subgroups, but could be
797 changed with C<Recursively> named argument.
803 my %args = ( Recursively => 1, @_ );
805 #If we don't have rights, don't include any results
806 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
808 my $members_table = $args{'Recursively'}?
809 'CachedGroupMembers': 'GroupMembers';
811 my $users = RT::Users->new($self->CurrentUser);
812 my $members_alias = $users->NewAlias( $members_table );
814 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
815 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id',
818 ALIAS => $members_alias,
820 VALUE => $self->PrincipalId,
823 ALIAS => $members_alias,
826 ) if $args{'Recursively'};
833 =head2 MemberEmailAddresses
835 Returns an array of the email addresses of all of this group's members
840 sub MemberEmailAddresses {
842 return sort grep defined && length,
843 map $_->EmailAddress,
844 @{ $self->UserMembersObj->ItemsArrayRef };
849 =head2 MemberEmailAddressesAsString
851 Returns a comma delimited string of the email addresses of all users
852 who are members of this group.
857 sub MemberEmailAddressesAsString {
859 return (join(', ', $self->MemberEmailAddresses));
864 =head2 AddMember PRINCIPAL_ID
866 AddMember adds a principal to this group. It takes a single principal id.
867 Returns a two value array. the first value is true on successful
868 addition or 0 on failure. The second value is a textual status msg.
874 my $new_member = shift;
878 # We should only allow membership changes if the user has the right
879 # to modify group membership or the user is the principal in question
880 # and the user has the right to modify his own membership
881 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
882 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
883 $self->CurrentUserHasRight('AdminGroupMembership') ) {
884 #User has no permission to be doing this
885 return ( 0, $self->loc("Permission Denied") );
888 $self->_AddMember(PrincipalId => $new_member);
891 # A helper subroutine for AddMember that bypasses the ACL checks
892 # this should _ONLY_ ever be called from Ticket/Queue AddWatcher
893 # when we want to deal with groups according to queue rights
894 # In the dim future, this will all get factored out and life
897 # takes a paramhash of { PrincipalId => undef, InsideTransaction }
901 my %args = ( PrincipalId => undef,
902 InsideTransaction => undef,
904 my $new_member = $args{'PrincipalId'};
907 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
908 return(0, $self->loc("Group not found"));
911 unless ($new_member =~ /^\d+$/) {
912 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
916 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
917 $new_member_obj->Load($new_member);
920 unless ( $new_member_obj->Id ) {
921 $RT::Logger->debug("Couldn't find that principal");
922 return ( 0, $self->loc("Couldn't find that principal") );
925 if ( $self->HasMember( $new_member_obj ) ) {
927 #User is already a member of this group. no need to add it
928 return ( 0, $self->loc("Group already has member: [_1]", $new_member_obj->Object->Name) );
930 if ( $new_member_obj->IsGroup &&
931 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
933 #This group can't be made to be a member of itself
934 return ( 0, $self->loc("Groups can't be members of their members"));
938 my $member_object = RT::GroupMember->new( $self->CurrentUser );
939 my $id = $member_object->Create(
940 Member => $new_member_obj,
941 Group => $self->PrincipalObj,
942 InsideTransaction => $args{'InsideTransaction'}
945 return ( 1, $self->loc("Member added: [_1]", $new_member_obj->Object->Name) );
948 return(0, $self->loc("Couldn't add member to group"));
953 =head2 HasMember RT::Principal|id
955 Takes an L<RT::Principal> object or its id returns a GroupMember Id if that user is a
956 member of this group.
957 Returns undef if the user isn't a member of the group or if the current
958 user doesn't have permission to find out. Arguably, it should differentiate
959 between ACL failure and non membership.
965 my $principal = shift;
968 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
969 $id = $principal->id;
970 } elsif ( $principal =~ /^\d+$/ ) {
973 $RT::Logger->error("Group::HasMember was called with an argument that".
974 " isn't an RT::Principal or id. It's ".($principal||'(undefined)'));
977 return undef unless $id;
979 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
980 $member_obj->LoadByCols(
982 GroupId => $self->PrincipalId
985 if ( my $member_id = $member_obj->id ) {
995 =head2 HasMemberRecursively RT::Principal|id
997 Takes an L<RT::Principal> object or its id and returns true if that user is a member of
999 Returns undef if the user isn't a member of the group or if the current
1000 user doesn't have permission to find out. Arguably, it should differentiate
1001 between ACL failure and non membership.
1005 sub HasMemberRecursively {
1007 my $principal = shift;
1010 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
1011 $id = $principal->id;
1012 } elsif ( $principal =~ /^\d+$/ ) {
1015 $RT::Logger->error("Group::HasMemberRecursively was called with an argument that".
1016 " isn't an RT::Principal or id. It's $principal");
1019 return undef unless $id;
1021 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1022 $member_obj->LoadByCols(
1024 GroupId => $self->PrincipalId
1027 if ( my $member_id = $member_obj->id ) {
1037 =head2 DeleteMember PRINCIPAL_ID
1039 Takes the principal id of a current user or group.
1040 If the current user has apropriate rights,
1041 removes that GroupMember from this group.
1042 Returns a two value array. the first value is true on successful
1043 addition or 0 on failure. The second value is a textual status msg.
1049 my $member_id = shift;
1052 # We should only allow membership changes if the user has the right
1053 # to modify group membership or the user is the principal in question
1054 # and the user has the right to modify his own membership
1056 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
1057 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1058 $self->CurrentUserHasRight('AdminGroupMembership') ) {
1059 #User has no permission to be doing this
1060 return ( 0, $self->loc("Permission Denied") );
1062 $self->_DeleteMember($member_id);
1065 # A helper subroutine for DeleteMember that bypasses the ACL checks
1066 # this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1067 # when we want to deal with groups according to queue rights
1068 # In the dim future, this will all get factored out and life
1073 my $member_id = shift;
1075 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1077 $member_obj->LoadByCols( MemberId => $member_id,
1078 GroupId => $self->PrincipalId);
1081 #If we couldn't load it, return undef.
1082 unless ( $member_obj->Id() ) {
1083 $RT::Logger->debug("Group has no member with that id");
1084 return ( 0,$self->loc( "Group has no such member" ));
1087 #Now that we've checked ACLs and sanity, delete the groupmember
1088 my $val = $member_obj->Delete();
1091 return ( $val, $self->loc("Member deleted") );
1094 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1095 return ( 0, $self->loc("Member not deleted" ));
1106 TransactionType => 'Set',
1107 RecordTransaction => 1,
1111 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
1112 return ( 0, $self->loc('Permission Denied') );
1115 my $Old = $self->SUPER::_Value("$args{'Field'}");
1117 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1118 Value => $args{'Value'} );
1120 #If we can't actually set the field to the value, don't record
1121 # a transaction. instead, get out of here.
1122 if ( $ret == 0 ) { return ( 0, $msg ); }
1124 if ( $args{'RecordTransaction'} == 1 ) {
1126 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1127 Type => $args{'TransactionType'},
1128 Field => $args{'Field'},
1129 NewValue => $args{'Value'},
1131 TimeTaken => $args{'TimeTaken'},
1133 return ( $Trans, scalar $TransObj->Description );
1136 return ( $ret, $msg );
1144 =head2 CurrentUserHasRight RIGHTNAME
1146 Returns true if the current user has the specified right for this group.
1149 TODO: we don't deal with membership visibility yet
1154 sub CurrentUserHasRight {
1161 $self->CurrentUser->HasRight( Object => $self,
1162 Right => $right )) {
1165 elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
1174 =head2 CurrentUserCanSee
1176 Always returns 1; unfortunately, for historical reasons, users have
1177 always been able to examine groups they have indirect access to, even if
1178 they do not have SeeGroup explicitly.
1182 sub CurrentUserCanSee {
1190 Returns the principal object for this user. returns an empty RT::Principal
1191 if there's no principal object matching this user.
1192 The response is cached. PrincipalObj should never ever change.
1200 unless ( defined $self->{'PrincipalObj'} &&
1201 defined $self->{'PrincipalObj'}->ObjectId &&
1202 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1203 (defined $self->{'PrincipalObj'}->PrincipalType &&
1204 $self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
1206 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1207 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1208 'PrincipalType' => 'Group') ;
1210 return($self->{'PrincipalObj'});
1216 Returns this user's PrincipalId
1229 [ Description => 'Description' ],
1236 Jesse Vincent, jesse@bestpractical.com
1250 Returns the current value of id.
1251 (In the database, id is stored as int(11).)
1259 Returns the current value of Name.
1260 (In the database, Name is stored as varchar(200).)
1264 =head2 SetName VALUE
1268 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1269 (In the database, Name will be stored as a varchar(200).)
1277 Returns the current value of Description.
1278 (In the database, Description is stored as varchar(255).)
1282 =head2 SetDescription VALUE
1285 Set Description to VALUE.
1286 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1287 (In the database, Description will be stored as a varchar(255).)
1295 Returns the current value of Domain.
1296 (In the database, Domain is stored as varchar(64).)
1300 =head2 SetDomain VALUE
1303 Set Domain to VALUE.
1304 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1305 (In the database, Domain will be stored as a varchar(64).)
1313 Returns the current value of Type.
1314 (In the database, Type is stored as varchar(64).)
1318 =head2 SetType VALUE
1322 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1323 (In the database, Type will be stored as a varchar(64).)
1331 Returns the current value of Instance.
1332 (In the database, Instance is stored as int(11).)
1336 =head2 SetInstance VALUE
1339 Set Instance to VALUE.
1340 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1341 (In the database, Instance will be stored as a int(11).)
1349 Returns the current value of Creator.
1350 (In the database, Creator is stored as int(11).)
1358 Returns the current value of Created.
1359 (In the database, Created is stored as datetime.)
1365 =head2 LastUpdatedBy
1367 Returns the current value of LastUpdatedBy.
1368 (In the database, LastUpdatedBy is stored as int(11).)
1376 Returns the current value of LastUpdated.
1377 (In the database, LastUpdated is stored as datetime.)
1384 sub _CoreAccessible {
1388 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1390 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
1392 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1394 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
1396 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
1398 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1400 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1402 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1404 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1406 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1411 RT::Base->_ImportOverlays();