Checking existing users with SFS

By Ronald van Belzen | May 27, 2018

At the moment that you start using Stop Forum Spam some of your subscribers may be known spammers at stopforumspam.com. They may have been dormant for some to time and start spamming when you least expect it. Also some subscribers to your site may have become known as spammers after they registered.

For this reason it may be wise to check your subscribers against the stopforumspam.com database. This is what the function checkUsers() of the SfsRequest class.

/* /src/SfsRequest */ 

 /**
   * Check registered user accounts of being spammers at www.stopforumspam.com.
   */
  public function checkUsers() {
    if (!$this->config->get('sfs_cron_job')) {
      return FALSE;
    }
    $lastUid = $this->config->get('sfs_cron_last_uid');
    $limit = $this->config->get('sfs_cron_account_limit');
    if ($limit > 0) {
      $query = $this->connection->select('users', 'e');
      $query->fields('e', ['uid']);
      $query->condition('uid', $lastUid, '>');
      $query->range(0, $limit);
      $query->orderBy('uid', 'ASC');
      $uids = $query->execute()->fetchCol();
      foreach ($uids as $uid) {
        $lastUid = $uid;
        $user = User::load($uid);
        $include = ($user->isActive() || $this->config->get('sfs_cron_blocked_accounts'));
        if ($include && !$user->hasPermission('exclude from sfs scans') && $this->userIsSpammer($user)) {
          try {
            $user->block();
            $user->save();
            $this->log->notice('User acount @uid has been disabled.', ['@uid' => $uid]);
          }
          catch (EntityStorageException $e) {
            $this->log->error('Failed to disable user acount @uid: @error', ['@uid' => $uid, '@error' => $e->getMessage()]);
          }
        }
      }
      $this->config->set('sfs_cron_last_uid', $lastUid);
      $this->config->save();
    }
    return TRUE;
  }

The function does not scan all subscribers in one run, but scans a limited amount of subscribers that the configuration setting 'sfs_cron_account_limit' allows. Inactive users can be excluded from the scan and users with the permission 'exclude from scans' are skipped too. User accounts of known spammers are disabled.

The actual check is done by the function userIsSpammer() for each individual user.

/* /src/SfsRequest */

  /**
   * @param User $user
   * @return boolean
   */
  public function userIsSpammer(User $user) {
    $name = $user->getAccountName();
    $mail = $user->getEmail();
    $ips = $this->getUserIpAddresses($user->id());
    
    if (empty($ips)) {
      return $this->isSpammer($name, $mail, NULL);
    }
    else {
      foreach ($ips as $ip) {
        if ($this->isSpammer($name, $mail, $ip)) {
          return TRUE;
        }
      }
    }
    
    return FALSE;
  }

The function isSpammer() has already been described. Here it is called for name and e-mail address, and for each ip address that can be found for a user. These ip addresses are attempted to extract from the sessions table and the posted content.

Now, because we only scan a limited amount of users in one run, we need to repeat the process a couple of time before all users are scannen, preferably automatically. For this we can use Drupal cron.

/* sfs.module */

/**
 * Implements hook_cron().
 */
function sfs_cron() {
  /** @var \Drupal\Core\Queue\QueueFactory $queue_factory */
  $queue_factory = \Drupal::service('queue');
  
  /** @var \Drupal\Core\Queue\QueueInterface $queue */
  $queue = $queue_factory->get('sfs_queue_worker');
  
  $queue->createItem(NULL);
}

The cron function does not actually calls the function to run the scan, but places a queue worker in the queue. This queue worker does call the function to run the scan.

/* /src/Plugin/QueueWorker/SfsQueueWorker.php */

<?php
namespace Drupal\sfs\Plugin\QueueWorker;

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\sfs\SfsRequest;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Class SfsQueueWorker
 * 
 * @QueueWorker (
 *  id = "sfs_queue_worker",
 *  title = @Translation("Cron Stop Forum Spam Client"),
 *  cron = {"time" = 30},
 * )
 */
class SfsQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {
  /**
   * @var \Drupal\sfs\SfsRequest
   */
  protected $sfsRequest;
  
  public function __construct(array $configuration, $plugin_id, $plugin_definition, SfsRequest $sfs_request) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->sfsRequest = $sfs_request;
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $sfsRequest = $container->get('sfs.detect.spam');
    
    return new static($configuration, $plugin_id, $plugin_definition, $sfsRequest);
  }
  
  /**
   * {@inheritdoc}
   */
  public function processItem($data) {
    $this->sfsRequest->checkUsers();
  }
}

This concludes the series on blocking spam with the help of the stopforumspam.com database. The first version of this software has been release at https://www.drupal.org/project/sfs, which contains the bugfixes in the code that may have been present in the code presented in the past blog posts of this series.

Further reading

Add new comment