Browse Source

Merge remote-tracking branch 'xrstf/bitbucket'

Jordi Boggiano 12 years ago
parent
commit
cbc0877639

+ 3 - 0
app/Resources/FOSUserBundle/views/Profile/show.html.twig

@@ -18,6 +18,9 @@
             <p>You can use your API token to interact with the Packagist API.</p>
             <h1>GitHub Service Hook</h1>
             <p>Enabling the Packagist service hook ensures that your package will always be updated instantly when you push to GitHub. To do so you can go to your GitHub repository, click the "Admin" button, then "Service Hooks". Pick "Packagist" in the list, and add your API token (see above), plus your Packagist username if it is not the same as on GitHub. Check the "Active" box and submit the form.</p>
+            <h1>Bitbucket Service</h1>
+            <p><em>Note:</em> The service has currently not been enabled on Bitbucket's side, so please give it some time to become available.</p>
+            <p>To enable the Bitbucket service hook, go to your BitBucket repository, open the "Admin" tab and select "Services" in the menu. Pick "Packagist" in the list and add it to your repository. Afterwards, configure it like the GitHub service hook.</p>
         {% endif %}
 
         <h1>Your packages</h1>

+ 64 - 48
src/Packagist/WebBundle/Controller/ApiController.php

@@ -82,55 +82,16 @@ class ApiController extends Controller
      */
     public function githubPostReceive(Request $request)
     {
-        $payload = json_decode($request->request->get('payload'), true);
-        if (!$payload || !isset($payload['repository']['url'])) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
-        }
-
-        $username = $request->request->has('username') ?
-            $request->request->get('username') :
-            $request->query->get('username');
-
-        $apiToken = $request->request->has('apiToken') ?
-            $request->request->get('apiToken') :
-            $request->query->get('apiToken');
-
-        $updater = $this->get('packagist.package_updater');
-        $em = $this->get('doctrine.orm.entity_manager');
-        $user = $this->get('packagist.user_repository')
-            ->findOneBy(array('username' => $username, 'apiToken' => $apiToken));
-
-        if (!$user) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials',)), 403);
-        }
-
-        if (!preg_match('{(github.com/[\w.-]+/[\w.-]+?)(\.git)?$}', $payload['repository']['url'], $match)) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL',)), 406);
-        }
-
-        $payloadRepositoryChunk = $match[1];
-
-        $updated = false;
-        $config = Factory::createConfig();
-        $loader = new ValidatingArrayLoader(new ArrayLoader());
-        foreach ($user->getPackages() as $package) {
-            if (preg_match('{'.preg_quote($payloadRepositoryChunk).'(\.git)?$}', $package->getRepository())) {
-                set_time_limit(3600);
-                $updated = true;
-
-                $repository = new VcsRepository(array('url' => $package->getRepository()), new NullIO, $config);
-                $repository->setLoader($loader);
-                $package->setAutoUpdated(true);
-                $em->flush();
-                $updater->update($package, $repository);
-            }
-        }
-
-        if ($updated) {
-            return new Response('{"status": "success"}', 202);
-        }
+        return $this->receivePost($request, '{(^|//)(?P<url>github\.com/[\w.-]+/[\w.-]+?)(\.git)?$}', '(\.git)?$');
+    }
 
-        return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
+    /**
+     * @Route("/api/bitbucket", name="bitbucket_postreceive", defaults={"_format" = "json"})
+     * @Method({"POST"})
+     */
+    public function bitbucketPostReceive(Request $request)
+    {
+        return $this->receivePost($request, '{(^|//)(?P<url>bitbucket\.org/[\w.-]+/[\w.-]+?)/?$}', '/?$');
     }
 
     /**
@@ -176,4 +137,59 @@ class ApiController extends Controller
 
         return new Response('{"status": "success"}', 201);
     }
+
+    protected function receivePost(Request $request, $urlRegex, $optionalRepositorySuffix)
+    {
+        $payload = json_decode($request->request->get('payload'), true);
+        if (!$payload || !isset($payload['repository']['url'])) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
+        }
+
+        // try to parse the URL first to avoid the DB lookup on malformed requests
+        if (!preg_match($urlRegex, $payload['repository']['url'], $match)) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL',)), 406);
+        }
+
+        $payloadRepositoryChunk = $match['url'];
+
+        $username = $request->request->has('username') ?
+            $request->request->get('username') :
+            $request->query->get('username');
+
+        $apiToken = $request->request->has('apiToken') ?
+            $request->request->get('apiToken') :
+            $request->query->get('apiToken');
+
+        $user = $this->get('packagist.user_repository')
+            ->findOneBy(array('username' => $username, 'apiToken' => $apiToken));
+
+        if (!$user) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials',)), 403);
+        }
+
+        $updated = false;
+        $config = Factory::createConfig();
+        $loader = new ValidatingArrayLoader(new ArrayLoader());
+        $updater = $this->get('packagist.package_updater');
+        $em = $this->get('doctrine.orm.entity_manager');
+
+        foreach ($user->getPackages() as $package) {
+            if (preg_match('{'.preg_quote($payloadRepositoryChunk).$optionalRepositorySuffix.'}', $package->getRepository())) {
+                set_time_limit(3600);
+                $updated = true;
+
+                $repository = new VcsRepository(array('url' => $package->getRepository()), new NullIO, $config);
+                $repository->setLoader($loader);
+                $package->setAutoUpdated(true);
+                $em->flush();
+                $updater->update($package, $repository);
+            }
+        }
+
+        if ($updated) {
+            return new Response('{"status": "success"}', 202);
+        }
+
+        return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
+    }
 }

+ 58 - 1
src/Packagist/WebBundle/Tests/Controller/ApiControllerTest.php

@@ -55,4 +55,61 @@ class ApiControllerTest extends WebTestCase
         $client->request('POST', '/api/github?username=test&apiToken=token', array('payload' => $payload));
         $this->assertEquals(202, $client->getResponse()->getStatusCode());
     }
-}
+
+    /**
+     * @depends      testGithubFailsCorrectly
+     * @dataProvider urlProvider
+     */
+    public function testUrlDetection($endpoint, $url, $expectedOK)
+    {
+        $client = self::createClient();
+        $payload = json_encode(array('repository' => array('url' => $url)));
+
+        $client->request('POST', '/api/'.$endpoint.'?username=INVALID_USER&apiToken=INVALID_TOKEN', array('payload' => $payload));
+
+        $status = $client->getResponse()->getStatusCode();
+
+        if (!$expectedOK) {
+            $this->assertEquals(406, $status, 'POST method should return 406 "Not Acceptable" if an unknown URL was sent');
+        } else {
+            $this->assertEquals(403, $status, 'POST method should return 403 "Forbidden" for a valid URL with bad credentials.');
+        }
+    }
+
+    public function urlProvider()
+    {
+        return array(
+            // valid github URLs
+            array('github', 'github.com/user/repo', true),
+            array('github', 'github.com/user/repo.git', true),
+            array('github', '//github.com/user/repo', true),
+            array('github', 'http://github.com/user/repo', true),
+            array('github', 'https://github.com/user/repo', true),
+            array('github', 'https://github.com/user/repo.git', true),
+            array('github', 'git://github.com/user/repo', true),
+
+            // valid bitbucket URLs
+            array('bitbucket', 'bitbucket.org/user/repo', true),
+            array('bitbucket', '//bitbucket.org/user/repo', true),
+            array('bitbucket', 'http://bitbucket.org/user/repo', true),
+            array('bitbucket', 'https://bitbucket.org/user/repo', true),
+
+            // protocol is ignored, so these are okay, too
+            array('github', 'php://github.com/user/repository', true),
+            array('github', 'javascript://github.com/user/repository', true),
+
+            // invalid URLs
+            array('github', 'http://', false),
+            array('github', 'http://thisisnotgithub.com/user/repository', false),
+            array('github', 'http://thisisnotbitbucket.org/user/repository', false),
+            array('github', 'githubcom/user/repository', false),
+            array('github', 'githubXcom/user/repository', false),
+            array('github', 'https://github.com/user/', false),
+            array('github', 'https://github.com/user', false),
+            array('github', 'https://github.com/', false),
+            array('github', 'https://github.com', false),
+            array('bitbucket', 'bitbucketorg/user/repository', false),
+            array('bitbucket', 'bitbucketXorg/user/repository', false),
+        );
+    }
+}