X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FGroupMember.pm;h=9ac757613347aa247d76b4cb88a832f8a7e529e8;hp=f7fa73e9ecdfa75f637a39fb10f3e572a49c1399;hb=919e930aa9279b3c5cd12b593889cd6de79d67bf;hpb=b4b0c7e72d7eaee2fbfc7022022c9698323203dd diff --git a/rt/lib/RT/GroupMember.pm b/rt/lib/RT/GroupMember.pm index f7fa73e9e..9ac757613 100755 --- a/rt/lib/RT/GroupMember.pm +++ b/rt/lib/RT/GroupMember.pm @@ -1,40 +1,40 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +# +# # (Except where explicitly superseded by other copyright notices) -# -# +# +# # LICENSE: -# +# # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 or visit their web page on the internet at # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) -# +# # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that @@ -43,80 +43,350 @@ # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. -# +# # END BPS TAGGED BLOCK }}} -# Autogenerated by DBIx::SearchBuilder factory (by ) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! -# +=head1 NAME -use strict; + RT::GroupMember - a member of an RT Group +=head1 SYNOPSIS -=head1 NAME +RT::GroupMember should never be called directly. It should ONLY +only be accessed through the helper functions in RT::Group; -RT::GroupMember +If you're operating on an RT::GroupMember object yourself, you B +doing something wrong. + +=head1 DESCRIPTION -=head1 SYNOPSIS -=head1 DESCRIPTION =head1 METHODS + + + =cut + package RT::GroupMember; -use RT::Record; +use strict; +use warnings; -use vars qw( @ISA ); -@ISA= qw( RT::Record ); -sub _Init { - my $self = shift; +use base 'RT::Record'; - $self->Table('GroupMembers'); - $self->SUPER::_Init(@_); +sub Table {'GroupMembers'} + + +use RT::CachedGroupMembers; + + +=head2 Create { Group => undef, Member => undef } + +Add a Principal to the group Group. +if the Principal is a group, automatically inserts all +members of the principal into the cached members table recursively down. + +Both Group and Member are expected to be RT::Principal objects + +=cut + +sub Create { + my $self = shift; + my %args = ( + Group => undef, + Member => undef, + InsideTransaction => undef, + @_ + ); + + unless ($args{'Group'} && + UNIVERSAL::isa($args{'Group'}, 'RT::Principal') && + $args{'Group'}->Id ) { + + $RT::Logger->warning("GroupMember::Create called with a bogus Group arg"); + return (undef); + } + + unless($args{'Group'}->IsGroup) { + $RT::Logger->warning("Someone tried to add a member to a user instead of a group"); + return (undef); + } + + unless ($args{'Member'} && + UNIVERSAL::isa($args{'Member'}, 'RT::Principal') && + $args{'Member'}->Id) { + $RT::Logger->warning("GroupMember::Create called with a bogus Principal arg"); + return (undef); + } + + + #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. + # TODO what about the groups key cache? + RT::Principal->InvalidateACLCache(); + + $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'}); + + # We really need to make sure we don't add any members to this group + # that contain the group itself. that would, um, suck. + # (and recurse infinitely) Later, we can add code to check this in the + # cache and bail so we can support cycling directed graphs + + if ($args{'Member'}->IsGroup) { + my $member_object = $args{'Member'}->Object; + if ($member_object->HasMemberRecursively($args{'Group'})) { + $RT::Logger->debug("Adding that group would create a loop"); + $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); + return(undef); + } + elsif ( $args{'Member'}->Id == $args{'Group'}->Id) { + $RT::Logger->debug("Can't add a group to itself"); + $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); + return(undef); + } + } + + + my $id = $self->SUPER::Create( + GroupId => $args{'Group'}->Id, + MemberId => $args{'Member'}->Id + ); + + unless ($id) { + $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); + return (undef); + } + + my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); + my $cached_id = $cached_member->Create( + Member => $args{'Member'}, + Group => $args{'Group'}, + ImmediateParent => $args{'Group'}, + Via => '0' + ); + + + #When adding a member to a group, we need to go back + #and popuplate the CachedGroupMembers of all the groups that group is part of . + + my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser ); + + # find things which have the current group as a member. + # $group is an RT::Principal for the group. + $cgm->LimitToGroupsWithMember( $args{'Group'}->Id ); + $cgm->Limit( + SUBCLAUSE => 'filter', # dont't mess up with prev condition + FIELD => 'MemberId', + OPERATOR => '!=', + VALUE => 'main.GroupId', + QUOTEVALUE => 0, + ENTRYAGGREGATOR => 'AND', + ); + + while ( my $parent_member = $cgm->Next ) { + my $parent_id = $parent_member->MemberId; + my $via = $parent_member->Id; + my $group_id = $parent_member->GroupId; + + my $other_cached_member = + RT::CachedGroupMember->new( $self->CurrentUser ); + my $other_cached_id = $other_cached_member->Create( + Member => $args{'Member'}, + Group => $parent_member->GroupObj, + ImmediateParent => $parent_member->MemberObj, + Via => $parent_member->Id + ); + unless ($other_cached_id) { + $RT::Logger->err( "Couldn't add " . $args{'Member'} + . " as a submember of a supergroup" ); + $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); + return (undef); + } + } + + unless ($cached_id) { + $RT::Handle->Rollback() unless ($args{'InsideTransaction'}); + return (undef); + } + + $RT::Handle->Commit() unless ($args{'InsideTransaction'}); + + return ($id); } +=head2 _StashUser PRINCIPAL + +Create { Group => undef, Member => undef } + +Creates an entry in the groupmembers table, which lists a user +as a member of himself. This makes ACL checks a whole bunch easier. +This happens once on user create and never ever gets yanked out. +PRINCIPAL is expected to be an RT::Principal object for a user -=head2 Create PARAMHASH +This routine expects to be called inside a transaction by RT::User->Create -Create takes a hash of values and creates a row in the database: +=cut + +sub _StashUser { + my $self = shift; + my %args = ( + Group => undef, + Member => undef, + @_ + ); + + #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. + # TODO what about the groups key cache? + RT::Principal->InvalidateACLCache(); + + + # We really need to make sure we don't add any members to this group + # that contain the group itself. that would, um, suck. + # (and recurse infinitely) Later, we can add code to check this in the + # cache and bail so we can support cycling directed graphs + + my $id = $self->SUPER::Create( + GroupId => $args{'Group'}->Id, + MemberId => $args{'Member'}->Id, + ); + + unless ($id) { + return (undef); + } + + my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser ); + my $cached_id = $cached_member->Create( + Member => $args{'Member'}, + Group => $args{'Group'}, + ImmediateParent => $args{'Group'}, + Via => '0' + ); + + unless ($cached_id) { + return (undef); + } + + return ($id); +} + + + +=head2 Delete - int(11) 'GroupId'. - int(11) 'MemberId'. +Takes no arguments. deletes the currently loaded member from the +group in question. + +Expects to be called _outside_ a transaction =cut +sub Delete { + my $self = shift; + $RT::Handle->BeginTransaction(); -sub Create { + # Find all occurrences of this member as a member of this group + # in the cache and nuke them, recursively. + + # The following code will delete all Cached Group members + # where this member's group is _not_ the primary group + # (Ie if we're deleting C as a member of B, and B happens to be + # a member of A, will delete C as a member of A without touching + # C as a member of B + + my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser ); + + $cached_submembers->Limit( + FIELD => 'MemberId', + OPERATOR => '=', + VALUE => $self->MemberObj->Id + ); + + $cached_submembers->Limit( + FIELD => 'ImmediateParentId', + OPERATOR => '=', + VALUE => $self->GroupObj->Id + ); + + + + + + while ( my $item_to_del = $cached_submembers->Next() ) { + my $del_err = $item_to_del->Delete(); + unless ($del_err) { + $RT::Handle->Rollback(); + $RT::Logger->warning("Couldn't delete cached group submember ".$item_to_del->Id); + return (undef); + } + } + + my ($err, $msg) = $self->SUPER::Delete(); + unless ($err) { + $RT::Logger->warning("Couldn't delete cached group submember ".$self->Id); + $RT::Handle->Rollback(); + return (undef); + } + + #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. + # TODO what about the groups key cache? + RT::Principal->InvalidateACLCache(); + + $RT::Handle->Commit(); + return ($err); + +} + + + +=head2 MemberObj + +Returns an RT::Principal object for the Principal specified by $self->MemberId + +=cut + +sub MemberObj { my $self = shift; - my %args = ( - GroupId => '0', - MemberId => '0', + unless ( defined( $self->{'Member_obj'} ) ) { + $self->{'Member_obj'} = RT::Principal->new( $self->CurrentUser ); + $self->{'Member_obj'}->Load( $self->MemberId ) if ($self->MemberId); + } + return ( $self->{'Member_obj'} ); +} + + - @_); - $self->SUPER::Create( - GroupId => $args{'GroupId'}, - MemberId => $args{'MemberId'}, -); +=head2 GroupObj +Returns an RT::Principal object for the Group specified in $self->GroupId + +=cut + +sub GroupObj { + my $self = shift; + unless ( defined( $self->{'Group_obj'} ) ) { + $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser ); + $self->{'Group_obj'}->Load( $self->GroupId ); + } + return ( $self->{'Group_obj'} ); } + + + =head2 id -Returns the current value of id. +Returns the current value of id. (In the database, id is stored as int(11).) @@ -125,7 +395,7 @@ Returns the current value of id. =head2 GroupId -Returns the current value of GroupId. +Returns the current value of GroupId. (In the database, GroupId is stored as int(11).) @@ -133,7 +403,7 @@ Returns the current value of GroupId. =head2 SetGroupId VALUE -Set GroupId to VALUE. +Set GroupId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, GroupId will be stored as a int(11).) @@ -143,7 +413,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 MemberId -Returns the current value of MemberId. +Returns the current value of MemberId. (In the database, MemberId is stored as int(11).) @@ -151,7 +421,7 @@ Returns the current value of MemberId. =head2 SetMemberId VALUE -Set MemberId to VALUE. +Set MemberId to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. (In the database, MemberId will be stored as a int(11).) @@ -159,56 +429,64 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut +=head2 Creator -sub _CoreAccessible { - { - - id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, - GroupId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, - MemberId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, +Returns the current value of Creator. +(In the database, Creator is stored as int(11).) - } -}; + +=cut - eval "require RT::GroupMember_Overlay"; - if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Overlay.pm}) { - die $@; - }; +=head2 Created - eval "require RT::GroupMember_Vendor"; - if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Vendor.pm}) { - die $@; - }; +Returns the current value of Created. +(In the database, Created is stored as datetime.) - eval "require RT::GroupMember_Local"; - if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Local.pm}) { - die $@; - }; +=cut +=head2 LastUpdatedBy -=head1 SEE ALSO +Returns the current value of LastUpdatedBy. +(In the database, LastUpdatedBy is stored as int(11).) -This class allows "overlay" methods to be placed -into the following files _Overlay is for a System overlay by the original author, -_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. -These overlay files can contain new subs or subs to replace existing subs in this module. +=cut -Each of these files should begin with the line - no warnings qw(redefine); +=head2 LastUpdated -so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. +Returns the current value of LastUpdated. +(In the database, LastUpdated is stored as datetime.) -RT::GroupMember_Overlay, RT::GroupMember_Vendor, RT::GroupMember_Local =cut + +sub _CoreAccessible { + { + + id => + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + GroupId => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + MemberId => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + Creator => + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + Created => + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + LastUpdatedBy => + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + LastUpdated => + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + + } +}; + +RT::Base->_ImportOverlays(); + 1;