浏览代码

Add a way to mark vendors safe from spam

Jordi Boggiano 5 年之前
父节点
当前提交
4507089ca7

+ 39 - 2
src/Packagist/WebBundle/Controller/PackageController.php

@@ -9,6 +9,7 @@ use Packagist\WebBundle\Entity\Download;
 use Packagist\WebBundle\Entity\Package;
 use Packagist\WebBundle\Entity\PackageRepository;
 use Packagist\WebBundle\Entity\Version;
+use Packagist\WebBundle\Entity\Vendor;
 use Packagist\WebBundle\Entity\VersionRepository;
 use Packagist\WebBundle\Form\Model\MaintainerRequest;
 use Packagist\WebBundle\Form\Type\AbandonedType;
@@ -327,10 +328,40 @@ class PackageController extends Controller
         $data['packages'] = $paginator;
         $data['count'] = $count;
         $data['meta'] = $this->getPackagesMetadata($data['packages']);
+        $data['markSafeCsrfToken'] = $this->get('security.csrf.token_manager')->getToken('mark_safe');
 
         return $this->render('PackagistWebBundle:package:spam.html.twig', $data);
     }
 
+    /**
+     * @Route(
+     *     "/spam/nospam",
+     *     name="mark_nospam",
+     *     defaults={"_format"="html"},
+     *     methods={"POST"}
+     * )
+     */
+    public function markSafeAction(Request $req)
+    {
+        if (!$this->getUser() || !$this->isGranted('ROLE_ANTISPAM')) {
+            throw new NotFoundHttpException();
+        }
+
+        $expectedToken = $this->get('security.csrf.token_manager')->getToken('mark_safe')->getValue();
+
+        $vendors = array_filter((array) $req->request->get('vendor'));
+        if (!hash_equals($expectedToken, $req->request->get('token'))) {
+            throw new BadRequestHttpException('Invalid CSRF token');
+        }
+
+        $repo = $this->getDoctrine()->getRepository(Vendor::class);
+        foreach ($vendors as $vendor) {
+            $repo->verify($vendor);
+        }
+
+        return $this->redirectToRoute('view_spam');
+    }
+
     /**
      * @Template()
      * @Route(
@@ -434,8 +465,11 @@ class PackageController extends Controller
             $data['downloads'] = $this->get('packagist.download_manager')->getDownloads($package, null, true);
 
             if (!$package->isSuspect() && ($data['downloads']['total'] ?? 0) <= 10 && ($data['downloads']['views'] ?? 0) >= 100) {
-                $package->setSuspect('Too many views');
-                $repo->markPackageSuspect($package);
+                $vendorRepo = $this->getDoctrine()->getRepository(Vendor::class);
+                if (!$vendorRepo->isVerified($package->getVendor())) {
+                    $package->setSuspect('Too many views');
+                    $repo->markPackageSuspect($package);
+                }
             }
 
             if ($this->getUser()) {
@@ -462,6 +496,9 @@ class PackageController extends Controller
             )) {
             $data['deleteVersionCsrfToken'] = $this->get('security.csrf.token_manager')->getToken('delete_version');
         }
+        if ($this->isGranted('ROLE_ANTISPAM')) {
+            $data['markSafeCsrfToken'] = $this->get('security.csrf.token_manager')->getToken('mark_safe');
+        }
 
         return $data;
     }

+ 68 - 0
src/Packagist/WebBundle/Entity/Vendor.php

@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Composer\Factory;
+use Composer\IO\NullIO;
+use Composer\Repository\VcsRepository;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Composer\Repository\Vcs\GitHubDriver;
+
+/**
+ * @ORM\Entity(repositoryClass="Packagist\WebBundle\Entity\VendorRepository")
+ * @ORM\Table(
+ *     name="vendor",
+ *     indexes={
+ *         @ORM\Index(name="verified_idx",columns={"verified"})
+ *     }
+ * )
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class Vendor
+{
+    /**
+     * Unique vendor name
+     *
+     * @ORM\Id
+     * @ORM\Column(length=191)
+     */
+    private $name;
+
+    /**
+     * @ORM\Column(type="boolean")
+     */
+    private $verified = false;
+
+    public function setName(string $name)
+    {
+        $this->name = $name;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setVerified(bool $verified)
+    {
+        $this->verified = $verified;
+    }
+
+    public function getVerified(): bool
+    {
+        return $this->verified;
+    }
+}

+ 40 - 0
src/Packagist/WebBundle/Entity/VendorRepository.php

@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class VendorRepository extends EntityRepository
+{
+    public function isVerified(string $vendor): bool
+    {
+        $result = $this->getEntityManager()->getConnection()->fetchColumn('SELECT verified FROM vendor WHERE name = :vendor', ['vendor' => $vendor]);
+
+        return $result === '1';
+    }
+
+    public function verify(string $vendor)
+    {
+        $this->getEntityManager()->getConnection()->executeUpdate(
+            'INSERT INTO vendor (name, verified) VALUES (:vendor, 1) ON DUPLICATE KEY UPDATE verified=1',
+            ['vendor' => $vendor]
+        );
+        $this->getEntityManager()->getConnection()->executeUpdate(
+            'UPDATE package SET suspect = NULL WHERE name LIKE :vendor',
+            ['vendor' => $vendor.'/%']
+        );
+    }
+}

+ 10 - 1
src/Packagist/WebBundle/Resources/views/package/spam.html.twig

@@ -8,7 +8,7 @@
 
 {% block content %}
     <div class="row">
-        <div class="col-xs-12 package">
+        <div class="col-xs-9 package">
             <div class="package-header">
                 <h2 class="title">
                     Suspect Packages
@@ -16,6 +16,15 @@
                 </h2>
             </div>
         </div>
+        <div class="col-xs-3">
+            <form class="action" action="{{ path("mark_nospam") }}" method="POST">
+                {% for p in packages %}
+                    <input type="hidden" name="vendor[]" value="{{ p.name|vendor }}" />
+                {% endfor %}
+                <input type="hidden" name="token" value="{{ markSafeCsrfToken }}" />
+                <input class="btn btn-danger" type="submit" value="Mark Whole Page As Not Spam" />
+            </form>
+        </div>
     </div>
 
     <section class="row">

+ 8 - 1
src/Packagist/WebBundle/Resources/views/package/view_package.html.twig

@@ -29,7 +29,7 @@
     {{ package.description|truncate(300) }}
 {%- endblock %}
 
-{% set hasActions = is_granted('ROLE_EDIT_PACKAGES') or is_granted('ROLE_UPDATE_PACKAGES') or package.maintainers.contains(app.user) %}
+{% set hasActions = is_granted('ROLE_EDIT_PACKAGES') or is_granted('ROLE_ANTISPAM') or is_granted('ROLE_UPDATE_PACKAGES') or package.maintainers.contains(app.user) %}
 
 {% block content %}
     <div class="row">
@@ -117,6 +117,13 @@
                                     <input class="btn btn-primary" type="submit" value="Edit" />
                                 </form>
                             {% endif %}
+                            {% if is_granted('ROLE_ANTISPAM') and package.isSuspect() %}
+                                <form class="action" action="{{ path("mark_nospam") }}" method="POST">
+                                    <input type="hidden" name="vendor" value="{{ package.vendor }}" />
+                                    <input type="hidden" name="token" value="{{ markSafeCsrfToken }}" />
+                                    <input class="btn btn-danger" type="submit" value="Mark Not Spam" />
+                                </form>
+                            {% endif %}
                         </div>
                     {% endif %}
                 </div>

+ 6 - 0
src/Packagist/WebBundle/Twig/PackagistExtension.php

@@ -34,6 +34,7 @@ class PackagistExtension extends \Twig\Extension\AbstractExtension
         return array(
             new \Twig_SimpleFilter('prettify_source_reference', [$this, 'prettifySourceReference']),
             new \Twig_SimpleFilter('gravatar_hash', [$this, 'generateGravatarHash']),
+            new \Twig_SimpleFilter('vendor', [$this, 'getVendor']),
         );
     }
 
@@ -49,6 +50,11 @@ class PackagistExtension extends \Twig\Extension\AbstractExtension
         return 'packagist';
     }
 
+    public function getVendor(string $packageName): string
+    {
+        return preg_replace('{/.*$}', '', $packageName);
+    }
+
     public function numericTest($val)
     {
         return ctype_digit((string) $val);