diff options
Diffstat (limited to 'rt/lib/RT/Shredder/Plugin/Users.pm')
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Users.pm | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/rt/lib/RT/Shredder/Plugin/Users.pm b/rt/lib/RT/Shredder/Plugin/Users.pm new file mode 100644 index 000000000..2565fc546 --- /dev/null +++ b/rt/lib/RT/Shredder/Plugin/Users.pm @@ -0,0 +1,260 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# <jesse@bestpractical.com> +# +# (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 +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# 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 }}} + +package RT::Shredder::Plugin::Users; + +use strict; +use warnings FATAL => 'all'; +use base qw(RT::Shredder::Plugin::Base::Search); + +=head1 NAME + +RT::Shredder::Plugin::Users - search plugin for wiping users. + +=head1 ARGUMENTS + +=head2 status - string + +Status argument allow you to limit result set to C<disabled>, +C<enabled> or C<any> users. +B<< Default value is C<disabled>. >> + +=head2 name - mask + +User name mask. + +=head2 email - mask + +Email address mask. + +=head2 member_of - group identifier + +Using this option users that are members of a particular group can +be selected for deletion. Identifier is name of user defined group +or id of a group, as well C<Privileged> or <unprivileged> can used +to select people from system groups. + +=head2 replace_relations - user identifier + +When you delete user there are could be minor links to him in RT DB. +This option allow you to replace this links with link to other user. +This links are Creator and LastUpdatedBy, but NOT any watcher roles, +this means that if user is watcher(Requestor, Owner, +Cc or AdminCc) of the ticket or queue then link would be deleted. + +This argument could be user id or name. + +=head2 no_tickets - boolean + +If true then plugin looks for users who are not watchers (Owners, +Requestors, Ccs or AdminCcs) of any ticket. + +Before RT 3.8.5, users who were watchers of deleted tickets B<will be deleted> +when this option was enabled. Decision has been made that it's not correct +and you should either shred these deleted tickets, change watchers or +explicitly delete user by name or email. + +Note that found users still B<may have relations> with other objects, +for example via Creator or LastUpdatedBy fields, and you most probably +want to use C<replace_relations> option. + +=cut + +sub SupportArgs +{ + return $_[0]->SUPER::SupportArgs, + qw(status name email member_of replace_relations no_tickets); +} + +sub TestArgs +{ + my $self = shift; + my %args = @_; + if( $args{'status'} ) { + unless( $args{'status'} =~ /^(disabled|enabled|any)$/i ) { + return (0, "Status '$args{'status'}' is unsupported."); + } + } else { + $args{'status'} = 'disabled'; + } + if( $args{'email'} ) { + $args{'email'} = $self->ConvertMaskToSQL( $args{'email'} ); + } + if( $args{'name'} ) { + $args{'name'} = $self->ConvertMaskToSQL( $args{'name'} ); + } + if( $args{'member_of'} ) { + my $group = RT::Group->new( $RT::SystemUser ); + if ( $args{'member_of'} =~ /^(Everyone|Privileged|Unprivileged)$/i ) { + $group->LoadSystemInternalGroup( $args{'member_of'} ); + } + else { + $group->LoadUserDefinedGroup( $args{'member_of'} ); + } + unless ( $group->id ) { + return (0, "Couldn't load group '$args{'member_of'}'" ); + } + $args{'member_of'} = $group->id; + + } + if( $args{'replace_relations'} ) { + my $uid = $args{'replace_relations'}; + # XXX: it's possible that SystemUser is not available + my $user = RT::User->new( $RT::SystemUser ); + $user->Load( $uid ); + unless( $user->id ) { + return (0, "Couldn't load user '$uid'" ); + } + $args{'replace_relations'} = $user->id; + } + return $self->SUPER::TestArgs( %args ); +} + +sub Run +{ + my $self = shift; + my %args = ( Shredder => undef, @_ ); + my $objs = RT::Users->new( $RT::SystemUser ); + # XXX: we want preload only things we need, but later while + # logging we need all data, TODO envestigate this + # $objs->Columns(qw(id Name EmailAddress Lang Timezone + # Creator Created LastUpdated LastUpdatedBy)); + if( my $s = $self->{'opt'}{'status'} ) { + if( $s eq 'any' ) { + $objs->{'find_disabled_rows'} = 1; + } elsif( $s eq 'disabled' ) { + $objs->{'find_disabled_rows'} = 1; + $objs->Limit( + ALIAS => $objs->PrincipalsAlias, + FIELD => 'Disabled', + OPERATOR => '!=', + VALUE => '0', + ); + } else { + $objs->LimitToEnabled; + } + } + if( $self->{'opt'}{'email'} ) { + $objs->Limit( FIELD => 'EmailAddress', + OPERATOR => 'MATCHES', + VALUE => $self->{'opt'}{'email'}, + ); + } + if( $self->{'opt'}{'name'} ) { + $objs->Limit( FIELD => 'Name', + OPERATOR => 'MATCHES', + VALUE => $self->{'opt'}{'name'}, + ); + } + if( $self->{'opt'}{'member_of'} ) { + $objs->MemberOfGroup( $self->{'opt'}{'member_of'} ); + } + if( $self->{'opt'}{'no_tickets'} ) { + return $self->FilterWithoutTickets( + Shredder => $args{'Shredder'}, + Objects => $objs, + ); + } else { + if( $self->{'opt'}{'limit'} ) { + $objs->RowsPerPage( $self->{'opt'}{'limit'} ); + } + } + return (1, $objs); +} + +sub SetResolvers +{ + my $self = shift; + my %args = ( Shredder => undef, @_ ); + + if( $self->{'opt'}{'replace_relations'} ) { + my $uid = $self->{'opt'}{'replace_relations'}; + my $resolver = sub { + my %args = (@_); + my $t = $args{'TargetObject'}; + foreach my $method ( qw(Creator LastUpdatedBy) ) { + next unless $t->_Accessible( $method => 'read' ); + $t->__Set( Field => $method, Value => $uid ); + } + }; + $args{'Shredder'}->PutResolver( BaseClass => 'RT::User', Code => $resolver ); + } + return (1); +} + +sub FilterWithoutTickets { + my $self = shift; + my %args = ( + Shredder => undef, + Objects => undef, + @_, + ); + my $users = $args{Objects}; + $self->FetchNext( $users, 'init' ); + + my @res; + while ( my $user = $self->FetchNext( $users ) ) { + push @res, $user if $self->_WithoutTickets( $user ); + return (1, \@res) if $self->{'opt'}{'limit'} && @res >= $self->{'opt'}{'limit'}; + } + return (1, \@res); +} + +sub _WithoutTickets { + my ($self, $user) = @_; + my $tickets = RT::Tickets->new( $RT::SystemUser ); + $tickets->{'allow_deleted_search'} = 1; + $tickets->FromSQL( 'Watcher.id = '. $user->id ); + # HACK: we may use Count method which counts all records + # that match condtion, but we really want to know only that + # at least one record exist, so we fetch first row only + $tickets->RowsPerPage(1); + return !$tickets->First; +} + +1; |