2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2015 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 if ( $dupcheck->id && ( !$self->id || $self->id != $dupcheck->id ) ) {
533 return ( 0, $self->loc( "Group name '[_1]' is already in use", $value ) );
538 =head2 _CreateACLEquivalenceGroup { Principal }
540 A helper subroutine which creates a group containing only
541 an individual user. This gets used by the ACL system to check rights.
542 Yes, it denormalizes the data, but that's ok, as we totally win on performance.
544 Returns a tuple of (Id, Message). If id is 0, the create failed
548 sub _CreateACLEquivalenceGroup {
552 my $id = $self->_Create( Domain => 'ACLEquivalence',
554 Name => 'User '. $princ->Object->Id,
555 Description => 'ACL equiv. for user '.$princ->Object->Id,
556 Instance => $princ->Id,
557 InsideTransaction => 1,
558 _RecordTransaction => 0 );
560 $RT::Logger->crit("Couldn't create ACL equivalence group");
564 # We use stashuser so we don't get transactions inside transactions
565 # and so we bypass all sorts of cruft we don't need
566 my $aclstash = RT::GroupMember->new($self->CurrentUser);
567 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
571 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
572 # We call super delete so we don't get acl checked.
573 $self->SUPER::Delete();
582 =head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
584 A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
585 Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
586 Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
587 Instance is the id of the ticket or queue in question
589 This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
591 Returns a tuple of (Id, Message). If id is 0, the create failed
595 sub CreateRoleGroup {
597 my %args = ( Instance => undef,
602 unless (RT::Queue->IsRoleGroupType($args{Type})) {
603 return ( 0, $self->loc("Invalid Group Type") );
607 return ( $self->_Create( Domain => $args{'Domain'},
608 Instance => $args{'Instance'},
609 Type => $args{'Type'},
610 InsideTransaction => 1 ) );
624 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
625 return ( 0, 'Permission Denied' );
628 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
631 # Remove the principal object
632 # Remove this group from anything it's a member of.
633 # Remove all cached members of this group
634 # Remove any rights granted to this group
635 # remove any rights delegated by way of this group
637 return ( $self->SUPER::Delete(@_) );
641 =head2 SetDisabled BOOL
643 If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
644 It will not appear in most group listings.
646 This routine finds all the cached group members that are members of this group (recursively) and disables them.
655 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
656 return (0, $self->loc('Permission Denied'));
658 $RT::Handle->BeginTransaction();
659 $self->PrincipalObj->SetDisabled($val);
664 # Find all occurrences of this member as a member of this group
665 # in the cache and nuke them, recursively.
667 # The following code will delete all Cached Group members
668 # where this member's group is _not_ the primary group
669 # (Ie if we're deleting C as a member of B, and B happens to be
670 # a member of A, will delete C as a member of A without touching
673 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
675 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
677 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
678 # TODO what about the groups key cache?
679 RT::Principal->InvalidateACLCache();
683 while ( my $item = $cached_submembers->Next() ) {
684 my $del_err = $item->SetDisabled($val);
686 $RT::Handle->Rollback();
687 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
692 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
694 $RT::Handle->Commit();
696 return (1, $self->loc("Group disabled"));
698 return (1, $self->loc("Group enabled"));
708 $self->PrincipalObj->Disabled(@_);
713 =head2 DeepMembersObj
715 Returns an RT::CachedGroupMembers object of this group's members,
716 including all members of subgroups.
722 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
724 #If we don't have rights, don't include any results
725 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
726 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
728 return ( $members_obj );
736 Returns an RT::GroupMembers object of this group's direct members.
742 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
744 #If we don't have rights, don't include any results
745 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
746 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
748 return ( $members_obj );
754 =head2 GroupMembersObj [Recursively => 1]
756 Returns an L<RT::Groups> object of this group's members.
757 By default returns groups including all subgroups, but
758 could be changed with C<Recursively> named argument.
760 B<Note> that groups are not filtered by type and result
761 may contain as well system groups and others.
765 sub GroupMembersObj {
767 my %args = ( Recursively => 1, @_ );
769 my $groups = RT::Groups->new( $self->CurrentUser );
770 my $members_table = $args{'Recursively'}?
771 'CachedGroupMembers': 'GroupMembers';
773 my $members_alias = $groups->NewAlias( $members_table );
775 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
776 ALIAS2 => $groups->PrincipalsAlias, FIELD2 => 'id',
779 ALIAS => $members_alias,
781 VALUE => $self->PrincipalId,
784 ALIAS => $members_alias,
787 ) if $args{'Recursively'};
794 =head2 UserMembersObj
796 Returns an L<RT::Users> object of this group's members, by default
797 returns users including all members of subgroups, but could be
798 changed with C<Recursively> named argument.
804 my %args = ( Recursively => 1, @_ );
806 #If we don't have rights, don't include any results
807 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
809 my $members_table = $args{'Recursively'}?
810 'CachedGroupMembers': 'GroupMembers';
812 my $users = RT::Users->new($self->CurrentUser);
813 my $members_alias = $users->NewAlias( $members_table );
815 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
816 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id',
819 ALIAS => $members_alias,
821 VALUE => $self->PrincipalId,
824 ALIAS => $members_alias,
827 ) if $args{'Recursively'};
834 =head2 MemberEmailAddresses
836 Returns an array of the email addresses of all of this group's members
841 sub MemberEmailAddresses {
843 return sort grep defined && length,
844 map $_->EmailAddress,
845 @{ $self->UserMembersObj->ItemsArrayRef };
850 =head2 MemberEmailAddressesAsString
852 Returns a comma delimited string of the email addresses of all users
853 who are members of this group.
858 sub MemberEmailAddressesAsString {
860 return (join(', ', $self->MemberEmailAddresses));
865 =head2 AddMember PRINCIPAL_ID
867 AddMember adds a principal to this group. It takes a single principal id.
868 Returns a two value array. the first value is true on successful
869 addition or 0 on failure. The second value is a textual status msg.
875 my $new_member = shift;
879 # We should only allow membership changes if the user has the right
880 # to modify group membership or the user is the principal in question
881 # and the user has the right to modify his own membership
882 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
883 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
884 $self->CurrentUserHasRight('AdminGroupMembership') ) {
885 #User has no permission to be doing this
886 return ( 0, $self->loc("Permission Denied") );
889 $self->_AddMember(PrincipalId => $new_member);
892 # A helper subroutine for AddMember that bypasses the ACL checks
893 # this should _ONLY_ ever be called from Ticket/Queue AddWatcher
894 # when we want to deal with groups according to queue rights
895 # In the dim future, this will all get factored out and life
898 # takes a paramhash of { PrincipalId => undef, InsideTransaction }
902 my %args = ( PrincipalId => undef,
903 InsideTransaction => undef,
905 my $new_member = $args{'PrincipalId'};
908 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
909 return(0, $self->loc("Group not found"));
912 unless ($new_member =~ /^\d+$/) {
913 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
917 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
918 $new_member_obj->Load($new_member);
921 unless ( $new_member_obj->Id ) {
922 $RT::Logger->debug("Couldn't find that principal");
923 return ( 0, $self->loc("Couldn't find that principal") );
926 if ( $self->HasMember( $new_member_obj ) ) {
928 #User is already a member of this group. no need to add it
929 return ( 0, $self->loc("Group already has member: [_1]", $new_member_obj->Object->Name) );
931 if ( $new_member_obj->IsGroup &&
932 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
934 #This group can't be made to be a member of itself
935 return ( 0, $self->loc("Groups can't be members of their members"));
939 my $member_object = RT::GroupMember->new( $self->CurrentUser );
940 my $id = $member_object->Create(
941 Member => $new_member_obj,
942 Group => $self->PrincipalObj,
943 InsideTransaction => $args{'InsideTransaction'}
946 return ( 1, $self->loc("Member added: [_1]", $new_member_obj->Object->Name) );
949 return(0, $self->loc("Couldn't add member to group"));
954 =head2 HasMember RT::Principal|id
956 Takes an L<RT::Principal> object or its id returns a GroupMember Id if that user is a
957 member of this group.
958 Returns undef if the user isn't a member of the group or if the current
959 user doesn't have permission to find out. Arguably, it should differentiate
960 between ACL failure and non membership.
966 my $principal = shift;
969 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
970 $id = $principal->id;
971 } elsif ( $principal =~ /^\d+$/ ) {
974 $RT::Logger->error("Group::HasMember was called with an argument that".
975 " isn't an RT::Principal or id. It's ".($principal||'(undefined)'));
978 return undef unless $id;
980 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
981 $member_obj->LoadByCols(
983 GroupId => $self->PrincipalId
986 if ( my $member_id = $member_obj->id ) {
996 =head2 HasMemberRecursively RT::Principal|id
998 Takes an L<RT::Principal> object or its id and returns true if that user is a member of
1000 Returns undef if the user isn't a member of the group or if the current
1001 user doesn't have permission to find out. Arguably, it should differentiate
1002 between ACL failure and non membership.
1006 sub HasMemberRecursively {
1008 my $principal = shift;
1011 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
1012 $id = $principal->id;
1013 } elsif ( $principal =~ /^\d+$/ ) {
1016 $RT::Logger->error("Group::HasMemberRecursively was called with an argument that".
1017 " isn't an RT::Principal or id. It's $principal");
1020 return undef unless $id;
1022 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1023 $member_obj->LoadByCols(
1025 GroupId => $self->PrincipalId
1028 if ( my $member_id = $member_obj->id ) {
1038 =head2 DeleteMember PRINCIPAL_ID
1040 Takes the principal id of a current user or group.
1041 If the current user has apropriate rights,
1042 removes that GroupMember from this group.
1043 Returns a two value array. the first value is true on successful
1044 addition or 0 on failure. The second value is a textual status msg.
1050 my $member_id = shift;
1053 # We should only allow membership changes if the user has the right
1054 # to modify group membership or the user is the principal in question
1055 # and the user has the right to modify his own membership
1057 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
1058 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1059 $self->CurrentUserHasRight('AdminGroupMembership') ) {
1060 #User has no permission to be doing this
1061 return ( 0, $self->loc("Permission Denied") );
1063 $self->_DeleteMember($member_id);
1066 # A helper subroutine for DeleteMember that bypasses the ACL checks
1067 # this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1068 # when we want to deal with groups according to queue rights
1069 # In the dim future, this will all get factored out and life
1074 my $member_id = shift;
1076 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1078 $member_obj->LoadByCols( MemberId => $member_id,
1079 GroupId => $self->PrincipalId);
1082 #If we couldn't load it, return undef.
1083 unless ( $member_obj->Id() ) {
1084 $RT::Logger->debug("Group has no member with that id");
1085 return ( 0,$self->loc( "Group has no such member" ));
1088 #Now that we've checked ACLs and sanity, delete the groupmember
1089 my $val = $member_obj->Delete();
1092 return ( $val, $self->loc("Member deleted") );
1095 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1096 return ( 0, $self->loc("Member not deleted" ));
1107 TransactionType => 'Set',
1108 RecordTransaction => 1,
1112 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
1113 return ( 0, $self->loc('Permission Denied') );
1116 my $Old = $self->SUPER::_Value("$args{'Field'}");
1118 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1119 Value => $args{'Value'} );
1121 #If we can't actually set the field to the value, don't record
1122 # a transaction. instead, get out of here.
1123 if ( $ret == 0 ) { return ( 0, $msg ); }
1125 if ( $args{'RecordTransaction'} == 1 ) {
1127 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1128 Type => $args{'TransactionType'},
1129 Field => $args{'Field'},
1130 NewValue => $args{'Value'},
1132 TimeTaken => $args{'TimeTaken'},
1134 return ( $Trans, scalar $TransObj->Description );
1137 return ( $ret, $msg );
1145 =head2 CurrentUserHasRight RIGHTNAME
1147 Returns true if the current user has the specified right for this group.
1150 TODO: we don't deal with membership visibility yet
1155 sub CurrentUserHasRight {
1162 $self->CurrentUser->HasRight( Object => $self,
1163 Right => $right )) {
1166 elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
1175 =head2 CurrentUserCanSee
1177 Always returns 1; unfortunately, for historical reasons, users have
1178 always been able to examine groups they have indirect access to, even if
1179 they do not have SeeGroup explicitly.
1183 sub CurrentUserCanSee {
1191 Returns the principal object for this user. returns an empty RT::Principal
1192 if there's no principal object matching this user.
1193 The response is cached. PrincipalObj should never ever change.
1201 unless ( defined $self->{'PrincipalObj'} &&
1202 defined $self->{'PrincipalObj'}->ObjectId &&
1203 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1204 (defined $self->{'PrincipalObj'}->PrincipalType &&
1205 $self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
1207 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1208 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1209 'PrincipalType' => 'Group') ;
1211 return($self->{'PrincipalObj'});
1217 Returns this user's PrincipalId
1230 [ Description => 'Description' ],
1237 Jesse Vincent, jesse@bestpractical.com
1251 Returns the current value of id.
1252 (In the database, id is stored as int(11).)
1260 Returns the current value of Name.
1261 (In the database, Name is stored as varchar(200).)
1265 =head2 SetName VALUE
1269 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1270 (In the database, Name will be stored as a varchar(200).)
1278 Returns the current value of Description.
1279 (In the database, Description is stored as varchar(255).)
1283 =head2 SetDescription VALUE
1286 Set Description to VALUE.
1287 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1288 (In the database, Description will be stored as a varchar(255).)
1296 Returns the current value of Domain.
1297 (In the database, Domain is stored as varchar(64).)
1301 =head2 SetDomain VALUE
1304 Set Domain to VALUE.
1305 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1306 (In the database, Domain will be stored as a varchar(64).)
1314 Returns the current value of Type.
1315 (In the database, Type is stored as varchar(64).)
1319 =head2 SetType VALUE
1323 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1324 (In the database, Type will be stored as a varchar(64).)
1332 Returns the current value of Instance.
1333 (In the database, Instance is stored as int(11).)
1337 =head2 SetInstance VALUE
1340 Set Instance to VALUE.
1341 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1342 (In the database, Instance will be stored as a int(11).)
1350 Returns the current value of Creator.
1351 (In the database, Creator is stored as int(11).)
1359 Returns the current value of Created.
1360 (In the database, Created is stored as datetime.)
1366 =head2 LastUpdatedBy
1368 Returns the current value of LastUpdatedBy.
1369 (In the database, LastUpdatedBy is stored as int(11).)
1377 Returns the current value of LastUpdated.
1378 (In the database, LastUpdated is stored as datetime.)
1385 sub _CoreAccessible {
1389 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1391 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
1393 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1395 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
1397 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
1399 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1401 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1403 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1405 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1407 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1412 RT::Base->_ImportOverlays();