浏览代码

Allow wiping out spammer accounts in one click

Jordi Boggiano 6 年之前
父节点
当前提交
c3b979b1c8

+ 3 - 1
app/config/security.yml

@@ -57,6 +57,8 @@ security:
         ROLE_UPDATE_PACKAGES: ~
         ROLE_DELETE_PACKAGES: ~
         ROLE_EDIT_PACKAGES: ~
+        ROLE_ANTISPAM: ~
+        ROLE_SPAMMER: ~
 
-        ROLE_ADMIN:       [ ROLE_USER, ROLE_UPDATE_PACKAGES, ROLE_EDIT_PACKAGES, ROLE_DELETE_PACKAGES ]
+        ROLE_ADMIN:       [ ROLE_USER, ROLE_UPDATE_PACKAGES, ROLE_EDIT_PACKAGES, ROLE_DELETE_PACKAGES, ROLE_ANTISPAM ]
         ROLE_SUPERADMIN:  [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]

+ 10 - 0
src/Packagist/WebBundle/Command/IndexPackagesCommand.php

@@ -119,6 +119,16 @@ class IndexPackagesCommand extends ContainerAwareCommand
                     $output->writeln('['.sprintf('%'.strlen($total).'d', $current).'/'.$total.'] Indexing '.$package->getName());
                 }
 
+                // delete spam packages from the search index
+                if ($package->isAbandoned() && $package->getReplacementPackage() === 'spam/spam') {
+                    try {
+                        $index->deleteObject($package->getName());
+                        $idsToUpdate[] = $package->getId();
+                        continue;
+                    } catch (\AlgoliaSearch\AlgoliaException $e) {
+                    }
+                }
+
                 try {
                     $tags = $this->getTags($doctrine, $package);
 

+ 4 - 0
src/Packagist/WebBundle/Controller/ApiController.php

@@ -331,6 +331,10 @@ class ApiController extends Controller
         $user = $this->get('packagist.user_repository')
             ->findOneBy(array('username' => $username, 'apiToken' => $apiToken));
 
+        if ($user && !$user->isEnabled()) {
+            return null;
+        }
+
         return $user;
     }
 

+ 13 - 1
src/Packagist/WebBundle/Controller/PackageController.php

@@ -87,13 +87,17 @@ class PackageController extends Controller
      */
     public function submitPackageAction(Request $req)
     {
+        $user = $this->getUser();
+        if (!$user->isEnabled()) {
+            throw new AccessDeniedException();
+        }
+
         $package = new Package;
         $package->setEntityRepository($this->getDoctrine()->getRepository('PackagistWebBundle:Package'));
         $package->setRouter($this->get('router'));
         $form = $this->createForm(PackageType::class, $package, [
             'action' => $this->generateUrl('submit'),
         ]);
-        $user = $this->getUser();
         $package->addMaintainer($user);
 
         $form->handleRequest($req);
@@ -310,6 +314,10 @@ class PackageController extends Controller
             return $this->redirect($this->generateUrl('search', array('q' => $name, 'reason' => 'package_not_found')));
         }
 
+        if ($package->isAbandoned() && $package->getReplacementPackage() === 'spam/spam') {
+            throw new NotFoundHttpException('This is a spam package');
+        }
+
         if ('json' === $req->getRequestFormat()) {
             $data = $package->toArray($this->getDoctrine()->getRepository('PackagistWebBundle:Version'));
             $data['dependents'] = $repo->getDependentCount($package->getName());
@@ -525,6 +533,10 @@ class PackageController extends Controller
             return new Response(json_encode(array('status' => 'error', 'message' => 'Package not found',)), 404);
         }
 
+        if ($package->isAbandoned() && $package->getReplacementPackage() === 'spam/spam') {
+            throw new NotFoundHttpException('This is a spam package');
+        }
+
         $username = $req->request->has('username') ?
             $req->request->get('username') :
             $req->query->get('username');

+ 57 - 2
src/Packagist/WebBundle/Controller/UserController.php

@@ -49,6 +49,56 @@ class UserController extends Controller
         );
     }
 
+    /**
+     * @Route("/spammers/{name}/", name="mark_spammer")
+     * @ParamConverter("user", options={"mapping": {"name": "username"}})
+     * @Method({"POST"})
+     */
+    public function markSpammerAction(Request $req, User $user)
+    {
+        if (!$this->isGranted('ROLE_ANTISPAM')) {
+            throw new AccessDeniedException('This user can not mark others as spammers');
+        }
+
+        $form = $this->createFormBuilder(array())->getForm();
+
+        $form->submit($req->request->get('form'));
+        if ($form->isValid()) {
+            $user->addRole('ROLE_SPAMMER');
+            $user->setEnabled(false);
+            $this->get('fos_user.user_manager')->updateUser($user);
+            $doctrine = $this->getDoctrine();
+
+            $doctrine->getConnection()->executeUpdate(
+                'UPDATE package p JOIN maintainers_packages mp ON mp.package_id = p.id JOIN fos_user u ON mp.user_id = u.id
+                 SET abandoned = 1, replacementPackage = "spam/spam", description = "", readme = "", indexedAt = NULL, dumpedAt = "2100-01-01 00:00:00"
+                 WHERE u.id = :userId',
+                ['userId' => $user->getId()]
+            );
+
+            /** @var VersionRepository $versionRepo */
+            $versionRepo = $doctrine->getRepository('PackagistWebBundle:Version');
+            $packages = $doctrine
+                ->getRepository('PackagistWebBundle:Package')
+                ->getFilteredQueryBuilder(array('maintainer' => $user->getId()), true)
+                ->getQuery()->getResult();
+
+            foreach ($packages as $package) {
+                foreach ($package->getVersions() as $version) {
+                    $versionRepo->remove($version);
+                }
+            }
+
+            $this->getDoctrine()->getManager()->flush();
+
+            $this->get('session')->getFlashBag()->set('success', $user->getUsername().' has been marked as a spammer');
+        }
+
+        return $this->redirect(
+            $this->generateUrl("user_profile", array("name" => $user->getUsername()))
+        );
+    }
+
     /**
      * @param Request $req
      * @return Response
@@ -72,7 +122,6 @@ class UserController extends Controller
         );
     }
 
-
     /**
      * @Template()
      * @Route("/users/{name}/", name="user_profile")
@@ -82,11 +131,17 @@ class UserController extends Controller
     {
         $packages = $this->getUserPackages($req, $user);
 
-        return array(
+        $data = array(
             'packages' => $packages,
             'meta' => $this->getPackagesMetadata($packages),
             'user' => $user,
         );
+
+        if ($this->isGranted('ROLE_ANTISPAM')) {
+            $data['spammerForm'] = $this->createFormBuilder(array())->getForm()->createView();
+        }
+
+        return $data;
     }
 
     /**

+ 1 - 0
src/Packagist/WebBundle/Entity/PackageRepository.php

@@ -399,6 +399,7 @@ class PackageRepository extends EntityRepository
         $qb = $this->getEntityManager()->createQueryBuilder();
         $qb->select('p')
             ->from('Packagist\WebBundle\Entity\Package', 'p')
+            ->where('p.abandoned = false')
             ->orderBy('p.id', 'DESC');
 
         return $qb;

+ 6 - 0
src/Packagist/WebBundle/Package/SymlinkDumper.php

@@ -206,6 +206,12 @@ class SymlinkDumper
 
                 // prepare packages in memory
                 foreach ($packages as $package) {
+                    // skip spam packages in the dumper in case we do a forced full dump and prevent them from being dumped for a little while
+                    if ($package->isAbandoned() && $package->getReplacementPackage() === 'spam/spam') {
+                        $dumpTimeUpdates['2100-01-01 00:00:00'][] = $package->getId();
+                        continue;
+                    }
+
                     $affectedFiles = array();
                     $name = strtolower($package->getName());
 

+ 9 - 0
src/Packagist/WebBundle/Resources/views/User/profile.html.twig

@@ -8,10 +8,19 @@
 <h2 class="title">
     {{ user.username }}
     <small>
+        {% if is_granted('ROLE_ANTISPAM') and user.hasRole('ROLE_SPAMMER') %}
+            [SPAMMER]
+        {% endif %}
         {{ 'user.member_since'|trans }}: {{ user.createdAt|date('M d, Y') }}
         {%- if is_granted('ROLE_ADMIN') %}
             <a href="mailto:{{ user.email }}">{{ user.email }}</a>
         {%- endif %}
+        {%- if is_granted('ROLE_ANTISPAM') %}
+            <form class="delete action" action="{{ path('mark_spammer', {name: user.username}) }}" method="POST">
+                {{ form_widget(spammerForm._token) }}
+                <input class="btn btn-danger" type="submit" value="Mark as Spammer" />
+            </form>
+        {%- endif %}
     </small>
 </h2>