Skip to content

Commit

Permalink
Add security advisory endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Jun 24, 2022
1 parent 36bb97c commit 02ea3e0
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 3 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ $packagist->searchPackagesByType('composer-plugin');
$packagist->searchPackages('packagist', ['type' => 'library']);
```

### Pagination
#### Pagination
Searching for packages returns a paginated result. You can change the pagination settings by adding more parameters.

```php
Expand All @@ -89,6 +89,22 @@ $packagist->getPackage('spatie', 'packagist-api');
$packagist->getStatistics();
```

### Get security vulnerability advisories
```php
// Get advisories for specific packages
$packages = ['spatie/packagist-api'];
$advisories = $packagist->getAdvisories($packages);

// Get advisories for specific packages that were updated after some timestamp
// The $packages array can also be ommitted here to get ALL advisories updated after that timestamp
$packages = ['spatie/packagist-api'];
$advisories = $packagist->getAdvisories($packages, strtotime('2 weeks ago'));

// Get advisories only for specific versions of specific packages
$packages = ['spatie/packagist-api' => '2.0.2'];
$advisories = $packagist->getAdvisories($packages, null, true);
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"require": {
"php": "^7.3|^8.0",
"ext-json": "*",
"guzzlehttp/guzzle": "^7.0"
"guzzlehttp/guzzle": "^7.0",
"composer/semver": "^1.0|^2.0|^3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.4"
Expand All @@ -49,4 +50,4 @@
"test": "vendor/bin/phpunit",
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes"
}
}
}
89 changes: 89 additions & 0 deletions src/PackagistClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Spatie\Packagist;

use Composer\Semver\Semver;
use GuzzleHttp\Client;
use Spatie\Packagist\Exceptions\InvalidArgumentException;

Expand Down Expand Up @@ -84,6 +85,94 @@ public function getStatistics(): ?array
return $this->request('statistics.json');
}

/**
* Get security viulnerability advisories for specific packages and/or which have been updated since some timestamp.
*
* If $filterByVersion is true, the $packages array must have package names as keys and versions as values.
* If it is false, the $packages array can contain package names as values.
*
* @throws InvalidArgumentException if no packages and no updatedSince timestamp are passed in
*/
public function getAdvisories(array $packages = [], ?int $updatedSince = null, bool $filterByVersion = false): array
{
if (count($packages) === 0 && $updatedSince === null) {
throw new InvalidArgumentException(
'At least one package or an $updatedSince timestamp must be passed in.'
);
}

if (count($packages) === 0 && $filterByVersion) {
return [];
}

// Add updatedSince to query if passed in
$query = [];
if ($updatedSince !== null) {
$query['updatedSince'] = $updatedSince;
}
$options = [
'query' => array_filter($query),
];

// Add packages if appropriate
if (count($packages) > 0) {
$content = ['packages' => []];
foreach ($packages as $package => $version) {
if (is_numeric($package)) {
$package = $version;
}
$content['packages'][] = $package;
}
$options['headers']['Content-type'] = 'application/x-www-form-urlencoded';
$options['body'] = http_build_query($content);
}

// Get advisories from API
$response = $this->postRequest('api/security-advisories/', $options);
if ($response === null) {
return [];
}

$advisories = $response['advisories'];

if (count($advisories) > 0 && $filterByVersion) {
return $this->filterAdvisories($advisories, $packages);
}

return $advisories;
}

private function filterAdvisories(array $advisories, array $packages): array
{
$filteredAdvisories = [];
foreach ($packages as $package => $version) {
// Skip any packages with no declared versions
if (is_numeric($package)) {
continue;
}
// Filter advisories by version
if (array_key_exists($package, $advisories)) {
foreach ($advisories[$package] as $advisory) {
if (Semver::satisfies($version, $advisory['affectedVersions'])) {
$filteredAdvisories[$package][] = $advisory;
}
}
}
}
return $filteredAdvisories;
}

public function postRequest(string $resource, array $options = [], string $mode = PackagistUrlGenerator::API_MODE): ?array
{
$url = $this->url->make($resource, $mode);
$response = $this->client
->post($url, $options)
->getBody()
->getContents();

return json_decode($response, true);
}

public function request(string $resource, array $query = [], string $mode = PackagistUrlGenerator::API_MODE): ?array
{
$url = $this->url->make($resource, $mode);
Expand Down

0 comments on commit 02ea3e0

Please sign in to comment.