|
@@ -20,6 +20,7 @@ use Composer\Package\Loader\ValidatingArrayLoader;
|
|
|
use Composer\Package\Loader\ArrayLoader;
|
|
|
use Packagist\WebBundle\Package\Updater;
|
|
|
use Packagist\WebBundle\Entity\Package;
|
|
|
+use Packagist\WebBundle\Entity\User;
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
@@ -84,7 +85,17 @@ class ApiController extends Controller
|
|
|
*/
|
|
|
public function githubPostReceive(Request $request)
|
|
|
{
|
|
|
- return $this->receivePost($request, '{^(?:https?://|git://|git@)?(?P<domain>github\.com)[:/](?P<repo>[\w.-]+/[\w.-]+?)(?:\.git)?$}');
|
|
|
+ // parse the GitHub payload
|
|
|
+ $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);
|
|
|
+ }
|
|
|
+
|
|
|
+ $urlRegex = '{^(?:https?://|git://|git@)?(?P<host>github\.com)[:/](?P<path>[\w.-]+/[\w.-]+?)(?:\.git)?$}';
|
|
|
+ $repoUrl = $payload['repository']['url'];
|
|
|
+
|
|
|
+ return $this->receivePost($request, $repoUrl, $urlRegex);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -93,7 +104,17 @@ class ApiController extends Controller
|
|
|
*/
|
|
|
public function bitbucketPostReceive(Request $request)
|
|
|
{
|
|
|
- return $this->receivePost($request, '{^(?:https?://)?(?P<domain>bitbucket\.org)/(?P<repo>[\w.-]+/[\w.-]+?)/?$}');
|
|
|
+ // decode Bitbucket's POST payload
|
|
|
+ $payload = json_decode($request->request->get('payload'), true);
|
|
|
+
|
|
|
+ if (!$payload || !isset($payload['canon_url']) || !isset($payload['repository']['absolute_url'])) {
|
|
|
+ return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
|
|
|
+ }
|
|
|
+
|
|
|
+ $urlRegex = '{^(?:https?://)?(?P<host>bitbucket\.org)/(?P<path>[\w.-]+/[\w.-]+?)/?$}';
|
|
|
+ $repoUrl = $payload['canon_url'].$payload['repository']['absolute_url'];
|
|
|
+
|
|
|
+ return $this->receivePost($request, $repoUrl, $urlRegex);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -182,18 +203,83 @@ class ApiController extends Controller
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- protected function receivePost(Request $request, $urlRegex)
|
|
|
+ /**
|
|
|
+ * Perform the package update
|
|
|
+ *
|
|
|
+ * @param Request $request the current request
|
|
|
+ * @param string $url the repository's URL (deducted from the request)
|
|
|
+ * @param string $urlRegex the regex used to split the user packages into domain and path
|
|
|
+ * @return Response
|
|
|
+ */
|
|
|
+ protected function receivePost(Request $request, $url, $urlRegex)
|
|
|
{
|
|
|
- $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, $url)) {
|
|
|
+ return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL')), 406);
|
|
|
}
|
|
|
|
|
|
- // try to parse the URL first to avoid the DB lookup on malformed requests
|
|
|
- if (!preg_match($urlRegex, $payload['repository']['url'], $requestedRepo)) {
|
|
|
- return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL',)), 406);
|
|
|
+ // find the user
|
|
|
+ $user = $this->findUser($request);
|
|
|
+
|
|
|
+ if (!$user) {
|
|
|
+ return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials')), 403);
|
|
|
+ }
|
|
|
+
|
|
|
+ // try to find the user package
|
|
|
+ $package = $this->findPackageByUrl($user, $url, $urlRegex);
|
|
|
+
|
|
|
+ if (!$package) {
|
|
|
+ return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)')), 404);
|
|
|
+ }
|
|
|
+
|
|
|
+ // don't die if this takes a while
|
|
|
+ set_time_limit(3600);
|
|
|
+
|
|
|
+ // put both updating the database and scanning the repository in a transaction
|
|
|
+ $em = $this->get('doctrine.orm.entity_manager');
|
|
|
+ $updater = $this->get('packagist.package_updater');
|
|
|
+ $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);
|
|
|
+
|
|
|
+ try {
|
|
|
+ $em->transactional(function($em) use ($package, $updater, $io) {
|
|
|
+ // prepare dependencies
|
|
|
+ $config = Factory::createConfig();
|
|
|
+ $loader = new ValidatingArrayLoader(new ArrayLoader());
|
|
|
+
|
|
|
+ // prepare repository
|
|
|
+ $repository = new VcsRepository(array('url' => $package->getRepository()), $io, $config);
|
|
|
+ $repository->setLoader($loader);
|
|
|
+
|
|
|
+ // perform the actual update (fetch and re-scan the repository's source)
|
|
|
+ $updater->update($package, $repository);
|
|
|
+
|
|
|
+ // update the package entity
|
|
|
+ $package->setAutoUpdated(true);
|
|
|
+ $em->flush();
|
|
|
+ });
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ if ($e instanceof InvalidRepositoryException) {
|
|
|
+ $this->get('packagist.package_manager')->notifyUpdateFailure($package, $e, $io->getOutput());
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Response(json_encode(array(
|
|
|
+ 'status' => 'error',
|
|
|
+ 'message' => '['.get_class($e).'] '.$e->getMessage(),
|
|
|
+ 'details' => '<pre>'.$io->getOutput().'</pre>'
|
|
|
+ )), 400);
|
|
|
}
|
|
|
|
|
|
+ return new JsonResponse(array('status' => 'success'), 202);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Find a user by his username and API token
|
|
|
+ *
|
|
|
+ * @param Request $request
|
|
|
+ * @return User|null the found user or null otherwise
|
|
|
+ */
|
|
|
+ protected function findUser(Request $request)
|
|
|
+ {
|
|
|
$username = $request->request->has('username') ?
|
|
|
$request->request->get('username') :
|
|
|
$request->query->get('username');
|
|
@@ -205,49 +291,32 @@ class ApiController extends Controller
|
|
|
$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);
|
|
|
- }
|
|
|
+ return $user;
|
|
|
+ }
|
|
|
|
|
|
- $updated = false;
|
|
|
- $config = Factory::createConfig();
|
|
|
- $loader = new ValidatingArrayLoader(new ArrayLoader());
|
|
|
- $updater = $this->get('packagist.package_updater');
|
|
|
- $em = $this->get('doctrine.orm.entity_manager');
|
|
|
+ /**
|
|
|
+ * Find a user package given by its full URL
|
|
|
+ *
|
|
|
+ * @param User $user
|
|
|
+ * @param string $url
|
|
|
+ * @param string $urlRegex
|
|
|
+ * @return Package|null the found package or null otherwise
|
|
|
+ */
|
|
|
+ protected function findPackageByUrl(User $user, $url, $urlRegex)
|
|
|
+ {
|
|
|
+ if (!preg_match($urlRegex, $url, $matched)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
foreach ($user->getPackages() as $package) {
|
|
|
if (preg_match($urlRegex, $package->getRepository(), $candidate)
|
|
|
- && $candidate['domain'] === $requestedRepo['domain']
|
|
|
- && $candidate['repo'] === $requestedRepo['repo']
|
|
|
+ && $candidate['host'] === $matched['host']
|
|
|
+ && $candidate['path'] === $matched['path']
|
|
|
) {
|
|
|
- set_time_limit(3600);
|
|
|
- $updated = true;
|
|
|
-
|
|
|
- $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);
|
|
|
- $repository = new VcsRepository(array('url' => $package->getRepository()), $io, $config);
|
|
|
- $repository->setLoader($loader);
|
|
|
- $package->setAutoUpdated(true);
|
|
|
- $em->flush();
|
|
|
- try {
|
|
|
- $updater->update($package, $repository);
|
|
|
- } catch (\Exception $e) {
|
|
|
- if ($e instanceof InvalidRepositoryException) {
|
|
|
- $this->get('packagist.package_manager')->notifyUpdateFailure($package, $e, $io->getOutput());
|
|
|
- }
|
|
|
-
|
|
|
- return new Response(json_encode(array(
|
|
|
- 'status' => 'error',
|
|
|
- 'message' => '['.get_class($e).'] '.$e->getMessage(),
|
|
|
- 'details' => '<pre>'.$io->getOutput().'</pre>'
|
|
|
- )), 400);
|
|
|
- }
|
|
|
+ return $package;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|