#!/usr/bin/perl # # DW::Controller::Admin::Approve # # Interface for screening new accounts for spam content. # # Authors: # Jen Griffin # # Copyright (c) 2020 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::Admin::Approve; use strict; use DW::Controller; use DW::Routing; use DW::Template; use DW::FormErrors; my $privs = [ 'siteadmin:approvenew', # just for this page 'siteadmin:spamreports', # include existing antispam team 'suspend', # include anyone with generic suspend privs 'suspend:recent', # just for this page ]; DW::Controller::Admin->register_admin_page( '/', path => 'recent_accounts', ml_scope => '/admin/recent_accounts/review.tt', privs => $privs ); DW::Routing->register_string( '/admin/recent_accounts', \&approve_handler, app => 1 ); sub approve_handler { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => $privs ); return $rv unless $ok; my $scope = '/admin/recent_accounts/review.tt'; my $prop = LJ::get_prop( 'user', 'not_approved' ); return error_ml( "$scope.error.disabled", { sitenameshort => $LJ::SITENAMESHORT } ) unless $prop && LJ::is_enabled('approvenew'); my $r = $rv->{r}; my $remote = $rv->{remote}; my $form_args = $r->post_args; my $vars = {}; $vars->{can_suspend} = 1 if $remote->has_priv( 'suspend', '' ) || $remote->has_priv( 'suspend', 'recent' ); return DW::Template->render_template( 'admin/recent_accounts/review.tt', $vars ) unless $r->did_post; my $dbr = LJ::get_db_reader(); my $sth = $dbr->prepare( "SELECT userid FROM userproplite " . "WHERE upropid=? AND value=? ORDER BY userid LIMIT ?" ); my $get_users = sub { my ( $value, $limit ) = @_; $sth->execute( $prop->{id}, $value, $limit ); die $dbr->errstr if $dbr->err; my @uids; push @uids, $_->[0] while $_ = $sth->fetchrow_arrayref; return LJ::load_userids(@uids); # hashref }; if ( $form_args->{begin} ) { # get a user to review - we don't want to just get the first result, # high chance of collision with multiple active reviewers my $users = $get_users->( 1, 5 ); # make sure the accounts are at least an hour old - sorting # SELECT by userid means oldest accounts will be reviewed first my $now = time; foreach ( keys %$users ) { delete $users->{$_} if $users->{$_}->timecreate > $now - 3600; } my @ids = sort keys %$users; if ( my $num = scalar @ids ) { my $pickrand = int( rand($num) ); $vars->{user} = $users->{ $ids[$pickrand] }; $vars->{ago} = LJ::diff_ago_text( $vars->{user}->timecreate ); return DW::Template->render_template( 'admin/recent_accounts/user.tt', $vars, { no_sitescheme => 1 } ); } else { return error_ml("$scope.error.nousers"); } } if ( $form_args->{uid} ) { my $u = LJ::load_userid( $form_args->{uid} ); my $success = DW::FormErrors->new; my $value; # take action based on form_args - either clear or escalate $value = 0 if $form_args->{yes}; $value = 2 if $form_args->{no}; return error_ml('error.invalidform') unless defined $value; my $act = $value ? 'rejected' : 'approved'; $success->add( '', ".success.$act", { user => $u->ljuser_display } ); # reinforce the different colors for rejection vs approval $vars->{ $value ? 'errors' : 'success' } = $success; # force lookup in case someone else just changed this - we still show # the success message to the user, but don't perform the actions again delete $u->{"not_approved"}; $u->preload_props( { use_master => 1 }, "not_approved" ); if ( $u->prop('not_approved') && $u->prop('not_approved') == 1 ) { $u->set_prop( not_approved => $value ); my $msg = sprintf( "new account %s $act by %s", $u->user, $remote->user ); LJ::statushistory_add( $u, $remote, 'approvenew', $msg ); # I thought about automatically doing the suspension here if # $remote had suspend privs, but I think it's better to give the # user a chance to review their actions in case of fat fingering } return DW::Template->render_template( 'admin/recent_accounts/review.tt', $vars ); } if ( $form_args->{suspend} ) { return $r->redirect("$LJ::SITEROOT/admin/recent_accounts") unless $vars->{can_suspend}; # get a list of flagged users to review for suspension $vars->{users} = $get_users->( 2, 10 ); return DW::Template->render_template( 'admin/recent_accounts/suspend.tt', $vars ); } if ( $form_args->{do_suspend} ) { my @ids = split / /, $form_args->{uids}; return error_ml('error.invalidform') unless @ids && $vars->{can_suspend}; my $users = LJ::load_userids(@ids); my $success = DW::FormErrors->new; my $count = 0; foreach my $uid (@ids) { my $u = $users->{$uid}; next if $u->is_suspended; # already done if ( $form_args->{"user_$uid"} ) { $u->set_suspended( $remote, "from /admin/recent_accounts" ); $count++; } else { $u->set_prop( not_approved => 0 ); LJ::statushistory_add( $u, $remote, 'approvenew', sprintf( "new account %s approved by %s", $u->user, $remote->user ) ); } } $success->add( '', '.success.suspend', { count => $count } ); $vars->{success} = $success; return DW::Template->render_template( 'admin/recent_accounts/review.tt', $vars ); } return error_ml('error.invalidform'); # no form args we know how to handle } 1;