Browse Source

Allow manually configuring github hooks securely

Jordi Boggiano 6 years ago
parent
commit
2136cbf5c4

+ 26 - 3
src/Packagist/WebBundle/Controller/ApiController.php

@@ -285,11 +285,34 @@ class ApiController extends Controller
             return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL')), 406);
         }
 
-        // find the user
-        $user = $this->findUser($request);
-
         $packages = null;
+        $user = null;
         $autoUpdated = Package::AUTO_MANUAL_HOOK;
+
+        // manual hook set up with user API token as secret
+        if ($match['host'] === 'github.com' && $request->getContent() && $request->query->has('username') && $request->headers->has('X-Hub-Signature')) {
+            $username = $request->query->get('username');
+            $sig = $request->headers->get('X-Hub-Signature');
+            $user = $this->get('packagist.user_repository')->findOneByUsername($username);
+            if ($sig && $user && $user->isEnabled()) {
+                list($algo, $sig) = explode('=', $sig);
+                $expected = hash_hmac($algo, $request->getContent(), $user->getApiToken());
+                if (hash_equals($expected, $sig)) {
+                    $packages = $this->findPackagesByRepository('https://github.com/'.$match['path']);
+                    $autoUpdated = Package::AUTO_GITHUB_HOOK;
+                } else {
+                    return new Response(json_encode(array('status' => 'error', 'message' => 'Secret should be the Packagist API Token for the Packagist user "'.$username.'". Signature verification failed.')), 403);
+                }
+            } else {
+                $user = null;
+            }
+        }
+
+        if (!$user) {
+            // find the user
+            $user = $this->findUser($request);
+        }
+
         if (!$user && $match['host'] === 'github.com' && $request->getContent()) {
             $sig = $request->headers->get('X-Hub-Signature');
             if ($sig) {

+ 8 - 0
src/Packagist/WebBundle/Resources/views/About/about.html.twig

@@ -111,6 +111,14 @@ v2.0.4-p1</code></pre>
                 <li>Check <a href="https://packagist.org/profile/">your package list</a> to see if any has a warning about not being automatically synced.</li>
                 <li>If you still need to setup sync on some packages, try <a rel="nofollow noindex" href="{{ path('user_github_sync') }}">triggering a manual account sync</a> to have Packagist try to set up hooks on your account again. Note that archived repositories can not be setup as they are readonly in GitHub's API.</li>
             </ul>
+            <h4>Do not want to log in via GitHub and grant us webhook configuration access?</h4>
+            <p>You can configure a GitHub webhook manually by using the following values:</p>
+            <ul>
+                <li>Payload URL: <code>https://packagist.org/api/update-package?username={{ app.user.username|default('PACKAGIST_USERNAME') }}</code></li>
+                <li>Content Type: <code>application/json</code></li>
+                <li>Secret: your <a href="{{ path('fos_user_profile_show') }}">Packagist API Token</a></li>
+                <li>Which events? Just the <code>push</code> event is enough.</li>
+            </ul>
         </section>
 
         <section class="col-md-6">

+ 2 - 1
src/Packagist/WebBundle/Service/GitHubUserMigrationWorker.php

@@ -14,6 +14,7 @@ use GuzzleHttp\Psr7\Response;
 class GitHubUserMigrationWorker
 {
     const HOOK_URL = 'https://packagist.org/api/github';
+    const HOOK_URL_ALT = 'https://packagist.org/api/update-package';
 
     private $logger;
     private $doctrine;
@@ -101,7 +102,7 @@ class GitHubUserMigrationWorker
             $currentHooks = array_values(array_filter(
                 $hooks,
                 function ($hook) {
-                    return $hook['name'] === 'web' && strpos($hook['config']['url'], self::HOOK_URL) === 0;
+                    return $hook['name'] === 'web' && (strpos($hook['config']['url'], self::HOOK_URL) === 0 || strpos($hook['config']['url'], self::HOOK_URL_ALT) === 0);
                 }
             ));