diff --git a/.mdlrc b/.mdlrc
new file mode 100644
index 0000000..60ecd7f
--- /dev/null
+++ b/.mdlrc
@@ -0,0 +1,2 @@
+# Disable "MD013 Line length" and "MD029 Ordered list item prefix".
+rules "~MD013", "~MD029"
diff --git a/.phpcs.xml b/.phpcs.xml
new file mode 100644
index 0000000..15fce7a
--- /dev/null
+++ b/.phpcs.xml
@@ -0,0 +1,17 @@
+
+
+ ./src
+ ./tests
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Dockerfile b/Dockerfile
index b4e9447..c6a635b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# -----------------
-FROM composer:1.9 AS build-env
+FROM composer:1.9.1 AS build-env
COPY . /opt/ghsec-jira/
@@ -8,7 +8,7 @@ WORKDIR /opt/ghsec-jira
RUN composer install --prefer-dist --no-dev
# -----------------
-FROM php:7.3.12-alpine
+FROM php:7.4.1-alpine
COPY --from=build-env /opt/ghsec-jira/ /opt/ghsec-jira/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1950f8a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+.PHONY: check phpstan phpcs markdownlint
+
+check: phpstan phpcs markdownlint
+
+phpstan:
+ -vendor/bin/phpstan analyse .
+
+phpcs:
+ -vendor/bin/phpcs -s bin/ src/
+
+# gem install mdl
+markdownlint:
+ -mdl *.md
diff --git a/README.md b/README.md
index dd56174..45a65de 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# github-security-jira
-GitHub Action for mapping security alerts to Jira tickets.
+GitHub Action for mapping security alerts to Jira tickets.
## Setup
@@ -9,28 +9,26 @@ You need the following pieces set up to sync alerts with Jira:
1. Two repo secrets containing a GitHub access token and a Jira API token, respectively.
2. A workflow file which runs the action on a schedule, continually creating new tickets when necessary.
-
### Repo secrets
+
The `reload/github-security-jira` action requires you to [create two encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) in the repo:
1. A secret called `GitHubSecurityToken` which should contain a [Personal Access Token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) for the GitHub user under which this action should be executed. The token must include the `public_repo` scope if checking only public repos, or the `repo` scope for use on private repos. Also, the user must have [access to security alerts in the repo](https://help.github.com/en/github/managing-security-vulnerabilities/managing-alerts-for-vulnerable-dependencies-in-your-organization).
2. A secret called `JiraApiToken` containing an [API Token](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) for the Jira user that should be used to create tickets.
-
### Workflow file setup
+
The [GitHub workflow file](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/configuring-a-workflow#creating-a-workflow-file) should reside in any repo where you want to sync security alerts with Jira.
It has some required and some optional settings, which are passed to the action as environment variables:
- `GH_SECURITY_TOKEN`: A reference to the repo secret `GitHubSecurityToken` (**REQUIRED**)
- `JIRA_TOKEN`: A reference to the repo secret `JiraApiToken` (**REQUIRED**)
-- `JIRA_HOST`: The endpoint for your Jira instance, e.g. https://foo.atlassian.net (**REQUIRED**)
+- `JIRA_HOST`: The endpoint for your Jira instance, e.g. (**REQUIRED**)
- `JIRA_USER`: The ID of the Jira user which is associated with the 'JiraApiToken' secret, eg 'someuser@reload.dk' (**REQUIRED**)
- `JIRA_PROJECT`: The project key for the Jira project where issues should be created, eg `TEST` or `ABC`. (**REQUIRED**)
- `JIRA_ISSUE_TYPE`: Type of issue to create, e.g. `Security`. Defaults to `Bug`. (*Optional*)
-- `JIRA_WATCHERS`: Jira users to add as watchers to tickets. Use the [YAML block scalar literal style indicator with stripping chomping indicator](https://yaml-multiline.info/) (pipe and dash: `|-`) to add multiple watchers. (*Optional*)
-- `JIRA_RESTRICTED_GROUP`: If set, the action will add a restricted comment to the ticket, viewable by only this Jira group. (*Optional*)
-- `JIRA_RESTRICTED_COMMENT`: The comment to post. Use the YAML multiline operator for adding linebreaks to the comment. (*Optional, but required if group is set*)
+- `JIRA_WATCHERS`: Jira users to add as watchers to tickets. Separate multiple watchers with comma (no spaces).
Here is an example setup which runs this action every 6 hours.
@@ -54,16 +52,9 @@ jobs:
JIRA_USER: someuser@reload.dk
JIRA_PROJECT: ABC
JIRA_ISSUE_TYPE: Security
- JIRA_WATCHERS: |-
- someuser@reload.dk
- someotheruser@reload.dk
- JIRA_RESTRICTED_GROUP: Developers
- JIRA_RESTRICTED_COMMENT: |-
- Remember to evaluate severity here and set ticket priority.
- Check out the guide [in our wiki|https://foo.atlassian.net/wiki/]!
+ JIRA_WATCHERS: someuser@reload.dk,someotheruser@reload.dk
```
-
## Local development
Copy `docker-composer.override.example.yml` to `docker-composer.override.yml` and edit according to your settings.
diff --git a/composer.json b/composer.json
index 363acd9..0a417b4 100644
--- a/composer.json
+++ b/composer.json
@@ -4,14 +4,28 @@
"license": "MIT",
"require": {
"php": ">=7.2.0",
- "lesstif/php-jira-rest-client": "^1",
"softonic/graphql-client": "^1.2",
"symfony/console": "^4",
- "symfony/yaml": "^5.0"
+ "symfony/yaml": "^5.0",
+ "reload/jira-security-issue": "dev-master"
},
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/appocular/coding-standard"
+ },
+ {
+ "type": "vcs",
+ "url": "https://github.com/reload/jira-security-issue"
+ }
+ ],
"autoload": {
"psr-4": {
"GitHubSecurityJira\\": "src/"
}
+ },
+ "require-dev": {
+ "appocular/coding-standard": "^1.0",
+ "phpstan/phpstan": "^0.12.5"
}
}
diff --git a/composer.lock b/composer.lock
index 91fb5e3..e240d14 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,53 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1b41138cfb13498aaabe6ccf84dda64b",
+ "content-hash": "c9ead59112aebd7e19abc06fc7bb932e",
"packages": [
- {
- "name": "appocular/coding-standard",
- "version": "1.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/appocular/coding-standard.git",
- "reference": "84cc48dc7552b8452e44189860e9db7ea5c4fd85"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/appocular/coding-standard/zipball/84cc48dc7552b8452e44189860e9db7ea5c4fd85",
- "reference": "84cc48dc7552b8452e44189860e9db7ea5c4fd85",
- "shasum": ""
- },
- "require": {
- "slevomat/coding-standard": "^5.0",
- "squizlabs/php_codesniffer": "^3.5"
- },
- "type": "project",
- "autoload": {
- "psr-4": {
- "Appocular\\CodingStandard\\": "AppocularCodingStandard"
- }
- },
- "license": [
- "MIT"
- ],
- "description": "Appocular PHP coding standard, PHP Code Sniffer rules.",
- "keywords": [
- "CodeSniffer",
- "PHPCodeSniffer",
- "coding",
- "coding standard",
- "cs",
- "phpcs",
- "ruleset",
- "sniffer",
- "standard"
- ],
- "support": {
- "source": "https://github.com/appocular/coding-standard/tree/1.0.3",
- "issues": "https://github.com/appocular/coding-standard/issues"
- },
- "time": "2019-11-17T18:59:56+00:00"
- },
{
"name": "guzzlehttp/guzzle",
"version": "6.4.1",
@@ -308,24 +263,24 @@
},
{
"name": "lesstif/php-jira-rest-client",
- "version": "1.41.0",
+ "version": "1.43.0",
"source": {
"type": "git",
"url": "https://github.com/lesstif/php-jira-rest-client.git",
- "reference": "12e0e7c4723eb2d5952e8360c35c955fa64d1cf5"
+ "reference": "fd07dfa55e86b551d5c37c081184a49084844132"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lesstif/php-jira-rest-client/zipball/12e0e7c4723eb2d5952e8360c35c955fa64d1cf5",
- "reference": "12e0e7c4723eb2d5952e8360c35c955fa64d1cf5",
+ "url": "https://api.github.com/repos/lesstif/php-jira-rest-client/zipball/fd07dfa55e86b551d5c37c081184a49084844132",
+ "reference": "fd07dfa55e86b551d5c37c081184a49084844132",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
- "monolog/monolog": "~1.12",
+ "monolog/monolog": "~1.12|^2.0",
"netresearch/jsonmapper": "~0.11|^1.0",
- "php": ">=5.5.9",
+ "php": ">=5.6.9",
"vlucas/phpdotenv": "~1.0|~2.0|^3.0"
},
"require-dev": {
@@ -354,7 +309,7 @@
{
"name": "KwangSeob Jeong",
"email": "lesstif@gmail.com",
- "homepage": "http://lesstif.com/"
+ "homepage": "https://lesstif.com/"
}
],
"description": "JIRA REST API Client for PHP Users.",
@@ -364,25 +319,25 @@
"jira-rest",
"rest"
],
- "time": "2019-07-30T12:31:28+00:00"
+ "time": "2020-01-14T09:25:33+00:00"
},
{
"name": "monolog/monolog",
- "version": "1.25.2",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287"
+ "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d5e2fb341cb44f7e2ab639d12a1e5901091ec287",
- "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8",
+ "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8",
"shasum": ""
},
"require": {
- "php": ">=5.3.0",
- "psr/log": "~1.0"
+ "php": "^7.2",
+ "psr/log": "^1.0.1"
},
"provide": {
"psr/log-implementation": "1.0.0"
@@ -390,33 +345,36 @@
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
- "graylog2/gelf-php": "~1.0",
- "jakub-onderka/php-parallel-lint": "0.9",
+ "elasticsearch/elasticsearch": "^6.0",
+ "graylog2/gelf-php": "^1.4.2",
+ "jakub-onderka/php-parallel-lint": "^0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
- "phpunit/phpunit": "~4.5",
- "phpunit/phpunit-mock-objects": "2.3.0",
+ "phpspec/prophecy": "^1.6.1",
+ "phpunit/phpunit": "^8.3",
+ "predis/predis": "^1.1",
+ "rollbar/rollbar": "^1.3",
"ruflin/elastica": ">=0.90 <3.0",
- "sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
- "ext-mongo": "Allow sending log messages to a MongoDB server",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
- "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
- "sentry/sentry": "Allow sending log messages to a Sentry server"
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "2.x-dev"
}
},
"autoload": {
@@ -442,7 +400,7 @@
"logging",
"psr-3"
],
- "time": "2019-11-13T10:00:05+00:00"
+ "time": "2019-12-20T14:22:59+00:00"
},
{
"name": "netresearch/jsonmapper",
@@ -537,33 +495,34 @@
},
{
"name": "phpoption/phpoption",
- "version": "1.5.2",
+ "version": "1.7.2",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
- "reference": "2ba2586380f8d2b44ad1b9feb61c371020b27793"
+ "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/2ba2586380f8d2b44ad1b9feb61c371020b27793",
- "reference": "2ba2586380f8d2b44ad1b9feb61c371020b27793",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
+ "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": "^5.5.9 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.7|^5.0"
+ "bamarni/composer-bin-plugin": "^1.3",
+ "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.5-dev"
+ "dev-master": "1.7-dev"
}
},
"autoload": {
- "psr-0": {
- "PhpOption\\": "src/"
+ "psr-4": {
+ "PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -574,6 +533,10 @@
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com"
}
],
"description": "Option Type for PHP",
@@ -583,54 +546,7 @@
"php",
"type"
],
- "time": "2019-11-06T22:27:00+00:00"
- },
- {
- "name": "phpstan/phpdoc-parser",
- "version": "0.3.5",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/8c4ef2aefd9788238897b678a985e1d5c8df6db4",
- "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4",
- "shasum": ""
- },
- "require": {
- "php": "~7.1"
- },
- "require-dev": {
- "consistence/coding-standard": "^3.5",
- "jakub-onderka/php-parallel-lint": "^0.9.2",
- "phing/phing": "^2.16.0",
- "phpstan/phpstan": "^0.10",
- "phpunit/phpunit": "^6.3",
- "slevomat/coding-standard": "^4.7.2",
- "squizlabs/php_codesniffer": "^3.3.2",
- "symfony/process": "^3.4 || ^4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "0.3-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "PHPStan\\PhpDocParser\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "PHPDoc parser with support for nullable, intersection and generic types",
- "time": "2019-06-07T19:13:52+00:00"
+ "time": "2019-12-15T19:35:24+00:00"
},
{
"name": "psr/cache",
@@ -865,44 +781,50 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
- "name": "slevomat/coding-standard",
- "version": "5.0.4",
+ "name": "reload/jira-security-issue",
+ "version": "dev-master",
"source": {
"type": "git",
- "url": "https://github.com/slevomat/coding-standard.git",
- "reference": "287ac3347c47918c0bf5e10335e36197ea10894c"
+ "url": "https://github.com/reload/jira-security-issue.git",
+ "reference": "7f20393cc7a04e051832b4f9674837648e59da2a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/287ac3347c47918c0bf5e10335e36197ea10894c",
- "reference": "287ac3347c47918c0bf5e10335e36197ea10894c",
+ "url": "https://api.github.com/repos/reload/jira-security-issue/zipball/7f20393cc7a04e051832b4f9674837648e59da2a",
+ "reference": "7f20393cc7a04e051832b4f9674837648e59da2a",
"shasum": ""
},
"require": {
- "php": "^7.1",
- "phpstan/phpdoc-parser": "^0.3.1",
- "squizlabs/php_codesniffer": "^3.4.1"
+ "lesstif/php-jira-rest-client": "^1.42",
+ "php": ">=7.2.0"
},
"require-dev": {
- "jakub-onderka/php-parallel-lint": "1.0.0",
- "phing/phing": "2.16.1",
- "phpstan/phpstan": "0.11.4",
- "phpstan/phpstan-phpunit": "0.11",
- "phpstan/phpstan-strict-rules": "0.11",
- "phpunit/phpunit": "8.0.5"
+ "appocular/coding-standard": "^1.0",
+ "phpstan/phpstan": "^0.12.4",
+ "phpunit/phpunit": "^8.5",
+ "sempro/phpunit-pretty-print": "^1.2",
+ "symfony/console": "^5.0"
},
- "type": "phpcodesniffer-standard",
+ "type": "library",
"autoload": {
"psr-4": {
- "SlevomatCodingStandard\\": "SlevomatCodingStandard"
+ "Reload\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Reload\\": "tests/"
}
},
- "notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
- "time": "2019-03-22T19:10:53+00:00"
+ "description": "Create Jira issues if it doesn't exist",
+ "support": {
+ "source": "https://github.com/reload/jira-security-issue/tree/master",
+ "issues": "https://github.com/reload/jira-security-issue/issues"
+ },
+ "time": "2020-01-09T13:28:51+00:00"
},
{
"name": "softonic/graphql-client",
@@ -989,57 +911,6 @@
],
"time": "2019-08-07T08:34:15+00:00"
},
- {
- "name": "squizlabs/php_codesniffer",
- "version": "3.5.2",
- "source": {
- "type": "git",
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
- "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
- "shasum": ""
- },
- "require": {
- "ext-simplexml": "*",
- "ext-tokenizer": "*",
- "ext-xmlwriter": "*",
- "php": ">=5.4.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
- },
- "bin": [
- "bin/phpcs",
- "bin/phpcbf"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Greg Sherwood",
- "role": "lead"
- }
- ],
- "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
- "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
- "keywords": [
- "phpcs",
- "standards"
- ],
- "time": "2019-10-28T04:36:32+00:00"
- },
{
"name": "symfony/console",
"version": "v4.4.0",
@@ -1118,7 +989,7 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.13.0",
+ "version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -1466,10 +1337,235 @@
"time": "2019-09-10T21:37:39+00:00"
}
],
- "packages-dev": [],
+ "packages-dev": [
+ {
+ "name": "appocular/coding-standard",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/appocular/coding-standard.git",
+ "reference": "84cc48dc7552b8452e44189860e9db7ea5c4fd85"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/appocular/coding-standard/zipball/84cc48dc7552b8452e44189860e9db7ea5c4fd85",
+ "reference": "84cc48dc7552b8452e44189860e9db7ea5c4fd85",
+ "shasum": ""
+ },
+ "require": {
+ "slevomat/coding-standard": "^5.0",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "Appocular\\CodingStandard\\": "AppocularCodingStandard"
+ }
+ },
+ "license": [
+ "MIT"
+ ],
+ "description": "Appocular PHP coding standard, PHP Code Sniffer rules.",
+ "keywords": [
+ "CodeSniffer",
+ "PHPCodeSniffer",
+ "coding",
+ "coding standard",
+ "cs",
+ "phpcs",
+ "ruleset",
+ "sniffer",
+ "standard"
+ ],
+ "support": {
+ "source": "https://github.com/appocular/coding-standard/tree/1.0.3",
+ "issues": "https://github.com/appocular/coding-standard/issues"
+ },
+ "time": "2019-11-17T18:59:56+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "0.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/8c4ef2aefd9788238897b678a985e1d5c8df6db4",
+ "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.1"
+ },
+ "require-dev": {
+ "consistence/coding-standard": "^3.5",
+ "jakub-onderka/php-parallel-lint": "^0.9.2",
+ "phing/phing": "^2.16.0",
+ "phpstan/phpstan": "^0.10",
+ "phpunit/phpunit": "^6.3",
+ "slevomat/coding-standard": "^4.7.2",
+ "squizlabs/php_codesniffer": "^3.3.2",
+ "symfony/process": "^3.4 || ^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "time": "2019-06-07T19:13:52+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "0.12.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "71a20c18f06c53605251a00a8efe443fa85225d1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/71a20c18f06c53605251a00a8efe443fa85225d1",
+ "reference": "71a20c18f06c53605251a00a8efe443fa85225d1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.12-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "time": "2020-01-12T14:31:21+00:00"
+ },
+ {
+ "name": "slevomat/coding-standard",
+ "version": "5.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/slevomat/coding-standard.git",
+ "reference": "287ac3347c47918c0bf5e10335e36197ea10894c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/287ac3347c47918c0bf5e10335e36197ea10894c",
+ "reference": "287ac3347c47918c0bf5e10335e36197ea10894c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "phpstan/phpdoc-parser": "^0.3.1",
+ "squizlabs/php_codesniffer": "^3.4.1"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "1.0.0",
+ "phing/phing": "2.16.1",
+ "phpstan/phpstan": "0.11.4",
+ "phpstan/phpstan-phpunit": "0.11",
+ "phpstan/phpstan-strict-rules": "0.11",
+ "phpunit/phpunit": "8.0.5"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "SlevomatCodingStandard\\": "SlevomatCodingStandard"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
+ "time": "2019-03-22T19:10:53+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
+ "reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2019-10-28T04:36:32+00:00"
+ }
+ ],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {
+ "reload/jira-security-issue": 20
+ },
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..e610f19
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,5 @@
+parameters:
+ level: max
+ excludes_analyse:
+ - vendor
+ - tests
diff --git a/src/JiraIssue.php b/src/JiraIssue.php
deleted file mode 100644
index 47913ba..0000000
--- a/src/JiraIssue.php
+++ /dev/null
@@ -1,259 +0,0 @@
- 'Bug'];
-
- public function __construct(
- string $jira_host,
- string $jira_user,
- string $jira_password,
- string $jira_project,
- string $github_repo,
- string $package,
- string $safeVersion,
- string $vulnerableVersionRange
- ) {
- $this->jiraHost = $jira_host;
- $this->jiraUser = $jira_user;
- $this->jiraPassword = $jira_password;
- $this->jiraProject = $jira_project;
- $this->GitHubRepo = $github_repo;
- $this->package = $package;
- $this->safeVersion = $safeVersion;
- $this->vulnerableVersionRange = $vulnerableVersionRange;
-
- $this->issueService = new IssueService(new ArrayConfiguration([
- 'jiraHost' => $this->jiraHost,
- 'jiraUser' => $this->jiraUser,
- 'jiraPassword' => $this->jiraPassword,
- ]));
-
- $this->userService = new UserService(new ArrayConfiguration([
- 'jiraHost' => $this->jiraHost,
- 'jiraUser' => $this->jiraUser,
- 'jiraPassword' => $this->jiraPassword,
- ]));
- }
-
- /**
- * Create the issue in Jira and set watchers and add a restricted comment.
- *
- * @return string|null
- * The ticket id of the created issue.
- */
- public function create(): ?string
- {
- $issueField = new IssueField();
- $issueField
- ->setProjectKey($this->jiraProject)
- ->setSummary("{$this->package} ({$this->safeVersion})")
- ->setDescription($this->getBody())
- ->setIssueType($this->getField('issue_type'))
- ->addLabel($this->GitHubRepo)
- ->addLabel($this->package)
- ->addLabel($this->getUniqueId());
-
- // Create issue.
- try {
- $issue = $this->issueService->create($issueField);
- } catch (\Throwable $t) {
- echo "Could not create issue {$this->project}:{$this->safeVersion}: {$t->getMessage()}" . PHP_EOL;
- return null;
- }
-
- // Add watchers.
- foreach ($this->getField('watchers') as $watchers_email) {
- try {
- $watchers_accountid = $this->getUserFieldFromEmail($watchers_email);
- $this->issueService->addWatcher($issue->key, $watchers_accountid);
- } catch (\Throwable $t) {
- echo "Could not add watcher {$watchers_email} to issue {$issue->key}: {$t->getMessage()}" . PHP_EOL;
- }
- }
-
- // Set restricted comment.
- if (!empty($this->getField('restricted_group')) && !empty($this->getField('restricted_comment'))) {
- try {
- $this->issueService->addComment($issue->key, $this->restrictedComment());
- } catch (\Throwable $t) {
- echo "Could not add comment to issue {$issue->key}: {$t->getMessage()}" . PHP_EOL;
- }
- }
-
- return $issue->key;
- }
-
- /**
- * Look up an existing issue in Jira.
- *
- * This method performs a JQL search in Jira to determine whether the
- * current issue already exists in this project. This is determined by
- * looking at the repo name and the uniqueId label.
- *
- * @return string|null
- * The ID of the issue found or null if no issue was found.
- */
- public function existingIssue(): ?string
- {
- $jql = "PROJECT = '{$this->jiraProject}' "
- . "AND labels IN ('{$this->GitHubRepo}') "
- . "AND labels IN ('{$this->getUniqueId()}') "
- . "ORDER BY created DESC";
- $result = $this->issueService->search($jql);
-
- if ($result->total > 0) {
- return reset($result->issues)->key;
- }
-
- return null;
- }
-
- public function setField(string $field, $value)
- {
- $this->fields[$field] = $value;
- }
-
- public function getField(string $field)
- {
- return $this->fields[$field] ?? null;
- }
-
- public function addField(string $field, string $value)
- {
- $this->fields[$field][] = trim($value);
- }
-
- /**
- * Method to generate a unique id for this alert.
- *
- * It uses the package name, the manifest base path (ie not including
- * 'composer.lock' or similar), and also the target version (safe version)
- * to create a string that is safe to use as eg a Jira label.
- *
- * @return string
- */
- protected function getUniqueId(): string
- {
- $strings[] = $this->package;
- if (!empty($this->getField('manifest_path'))) {
- $manifest_base_path = pathinfo($this->getField('manifest_path'), PATHINFO_DIRNAME);
- if (!empty($manifest_base_path) && $manifest_base_path != '.') {
- $strings[] = $manifest_base_path;
- }
- }
- $strings[] = $this->safeVersion;
- return implode(':', $strings);
- }
-
- /**
- * Return formatted body string.
- *
- * @return string
- */
- protected function getBody(): string
- {
- $advisory_description = wordwrap($this->getField('advisory_description'), 100);
- $references = implode(', ', $this->getField('references'));
- $ecosystem = $this->getField('ecosystem') ? '(' . $this->getField('ecosystem') . ')' : '';
- return <<GitHubRepo}|https://github.com/{$this->GitHubRepo}]
-- Package: {$this->package} $ecosystem
-- Severity: {$this->getField('severity')}
-- Vulnerable version: {$this->vulnerableVersionRange}
-- Secure version: {$this->safeVersion}
-- Links: {$references}
-{noformat}
-{$advisory_description}
-{noformat}
-EOT;
- }
-
- /**
- * Return array containing formatted values for restricted comment.
- *
- * @return array
- * The array containing settings and text for restricted comment.
- */
- protected function restrictedComment(): array
- {
- return [
- 'visibility' => [
- 'type' => 'role',
- 'value' => $this->getField('restricted_group'),
- ],
- 'body' => $this->getField('restricted_comment') . PHP_EOL
- . $this->formatWatchers($this->getField('watchers')),
- ];
- }
-
- protected function formatWatchers(array $watchers): string
- {
- if (empty($watchers)) {
- return '';
- }
-
- $watchers_keys = array_map(function (string $watchers_email) {
- return $this->getUserFieldFromEmail($watchers_email, 'key');
- }, $watchers);
- return 'Watchers: [~' . implode('], [~', $watchers_keys) . '].';
- }
-
- /**
- * Helper method to lookup a user in Jira.
- *
- * @param string $email
- * The email address to lookup.
- * @param string $user_field
- * The user field to return. Optional. Defaults to accountId.
- *
- * @return string|null
- * The user field value.
- */
- protected function getUserFieldFromEmail(string $email, string $user_field = 'accountId'): ?string
- {
- try {
- $paramArray = [
- 'query' => $email,
- 'project' => $this->jiraProject,
- 'maxResults' => 1
- ];
-
- $users = $this->userService->findAssignableUsers($paramArray);
-
- if (empty($users)) {
- return null;
- }
- } catch (JiraException $e) {
- echo "ERROR: Could not query Jira with email '${email}'. " . $e->getMessage();
- return null;
- }
-
- $user = array_pop($users);
- return $user->$user_field;
- }
-}
diff --git a/src/PullRequestIssue.php b/src/PullRequestIssue.php
new file mode 100644
index 0000000..42e402b
--- /dev/null
+++ b/src/PullRequestIssue.php
@@ -0,0 +1,62 @@
+ $data
+ */
+ public function __construct(array $data)
+ {
+ $this->package = \preg_replace('/.*Bump (.*) from.*/', '$1', $data['title']) ?? '';
+ $this->manifestPath = \preg_replace('/.* in \/(.*)/', '$1', $data['title']) ?? '';
+ $this->safeVersion = \preg_replace('/.*to (.*) in.*/', '$1', $data['title']) ?? '';
+
+ $githubRepo = \getenv('GITHUB_REPOSITORY') ?: '';
+
+ $body = <<package}
+- Secure version: {$this->safeVersion}
+- Pull request with more info: [#{$data['number']}|{$data['url']}]
+EOT;
+
+ parent::__construct();
+
+ $this->setKeyLabel($githubRepo);
+ $this->setKeyLabel($this->uniqueId());
+ $this->setTitle("{$this->package} ({$this->safeVersion})");
+ $this->setBody($body);
+ }
+
+ /**
+ * The unique ID of the severity.
+ *
+ * @return string
+ */
+ public function uniqueId(): string
+ {
+ return "{$this->package}:{$this->manifestPath}:{$this->safeVersion}";
+ }
+}
diff --git a/src/SecurityAlertIssue.php b/src/SecurityAlertIssue.php
new file mode 100644
index 0000000..57ec423
--- /dev/null
+++ b/src/SecurityAlertIssue.php
@@ -0,0 +1,100 @@
+ $data
+ */
+ public function __construct(array $data)
+ {
+ // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
+ $this->package = $data['securityVulnerability']['package']['name'];
+ $this->safeVersion = $data['securityVulnerability']['firstPatchedVersion']['identifier'];
+ $this->vulnerableVersionRange = $data['securityVulnerability']['vulnerableVersionRange'];
+ $this->manifestPath = \pathinfo($data['vulnerableManifestPath'], \PATHINFO_DIRNAME);
+
+ $references = [];
+
+ foreach ($data['securityVulnerability']['advisory']['references'] as $ref) {
+ if (!\array_key_exists('url', $ref) || !\is_string($ref['url'])) {
+ continue;
+ }
+
+ $references[] = $ref['url'];
+ }
+
+ $advisory_description = \wordwrap($data['securityVulnerability']['advisory']['description'] ?? '', 100);
+ $ecosystem = $data['securityVulnerability']['package']['ecosystem'] ?? '';
+ $githubRepo = \getenv('GITHUB_REPOSITORY') ?: '';
+
+ $body = <<package} ($ecosystem)
+- Vulnerable version: {$this->vulnerableVersionRange}
+- Secure version: {$this->safeVersion}
+
+EOT;
+
+ if (\is_array($references) && (\count($references) > 0)) {
+ $body .= "- Links: \n-- " . \implode("\n-- ", $references);
+ }
+
+ $body .= <<setKeyLabel($githubRepo);
+ $this->setKeyLabel($this->uniqueId());
+ $this->setTitle("{$this->package} ({$this->safeVersion})");
+ $this->setBody($body);
+ }
+
+ /**
+ * The unique ID of the severity.
+ *
+ * @return string
+ */
+ public function uniqueId(): string
+ {
+ if ($this->manifestPath === '.') {
+ return "{$this->package}:{$this->safeVersion}";
+ }
+
+ return "{$this->package}:{$this->manifestPath}:{$this->safeVersion}";
+ }
+}
diff --git a/src/SyncCommand.php b/src/SyncCommand.php
index 39657c7..4d39cea 100644
--- a/src/SyncCommand.php
+++ b/src/SyncCommand.php
@@ -4,13 +4,13 @@
namespace GitHubSecurityJira;
+use RuntimeException;
+use Softonic\GraphQL\Client as GraphQLClient;
+use Softonic\GraphQL\ClientBuilder;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Yaml\Yaml;
-use Softonic\GraphQL\ClientBuilder;
/**
* The default sync command.
@@ -25,6 +25,11 @@ class SyncCommand extends Command
*/
protected static $defaultName = 'sync';
+ /**
+ * Required options in environment variables.
+ *
+ * @var array
+ */
protected $requiredOptions = [
'GITHUB_REPOSITORY',
'GH_SECURITY_TOKEN',
@@ -45,7 +50,7 @@ public function __construct()
/**
* {@inheritDoc}
*/
- protected function configure()
+ protected function configure(): void
{
$this
->setDescription('Sync GitHub Alert status to Jira')
@@ -54,14 +59,16 @@ protected function configure()
'dry-run',
null,
InputOption::VALUE_NONE,
- 'Do dry run (dont change anything)'
+ 'Do dry run (dont change anything)',
);
}
/**
* {@inheritDoc}
+ *
+ * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
*/
- protected function initialize(InputInterface $input, OutputInterface $output)
+ protected function initialize(InputInterface $input, OutputInterface $output): void
{
// Validate config.
$this->validateConfig();
@@ -73,99 +80,67 @@ protected function initialize(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output)
{
- $jira_host = getenv('JIRA_HOST');
- $jira_user = getenv('JIRA_USER');
- $jira_password = getenv('JIRA_TOKEN');
- $jira_project = getenv('JIRA_PROJECT');
-
- $github_repo = getenv('GITHUB_REPOSITORY');
-
- $issue_type = getenv('JIRA_ISSUE_TYPE');
-
- $watchers = [];
- if (is_string(getenv('JIRA_WATCHERS'))) {
- $watchers = explode("\n", getenv('JIRA_WATCHERS')) ?? [];
- }
-
- $res_group = getenv('JIRA_RESTRICTED_GROUP');
- $res_comment = getenv('JIRA_RESTRICTED_COMMENT');
-
// Fetch alert data from GitHub.
$alerts = $this->fetchAlertData();
- if (empty($alerts)) {
- $this->logLine($output, 'No alerts found.');
+
+ if (!\is_array($alerts)) {
+ $this->log($output, 'No alerts found.');
}
+ $alertsFound = [];
+
// Go through each alert and create a Jira issue if one does not exist.
foreach ($alerts as $alert) {
- $package = $alert['securityVulnerability']['package']['name'];
- $safeVersion = $alert['securityVulnerability']['firstPatchedVersion']['identifier'];
- $vulnerableVersionRange = $alert['securityVulnerability']['vulnerableVersionRange'];
-
- $issue = new JiraIssue(
- $jira_host,
- $jira_user,
- $jira_password,
- $jira_project,
- $github_repo,
- $package,
- $safeVersion,
- $vulnerableVersionRange
- );
+ $issue = new SecurityAlertIssue($alert);
- $issue->setField('severity', $alert['securityVulnerability']['severity'] ?? '');
- $issue->setField('ecosystem', $alert['securityVulnerability']['package']['ecosystem'] ?? '');
- $issue->setField('advisory_description', $alert['securityVulnerability']['advisory']['description'] ?? '');
- $issue->setField('manifest_path', $alert['vulnerableManifestPath']);
- foreach ($alert['securityVulnerability']['advisory']['references'] as $ref) {
- if (!empty($ref['url'])) {
- $issue->addField('references', $ref['url']);
- }
- }
+ $existingKey = $issue->exists();
- if (!empty($issue_type)) {
- $issue->setField('issue_type', $issue_type);
- }
- $issue->setField('watchers', $watchers);
- $issue->setField('restricted_group', $res_group ?? '');
- $issue->setField('restricted_comment', $res_comment ?? []);
-
- $timestamp = gmdate(DATE_ISO8601);
- $this->log($output, "{$timestamp} - {$jira_project} - {$package}:{$vulnerableVersionRange} - ");
-
- // Determine whether there is an issue for this alert already.
- try {
- $key = $issue->existingIssue();
- } catch (\Throwable $t) {
- $this->logLine($output, "ERROR ACCESSING JIRA: {$t->getMessage()}.");
- exit(1);
+ if (!\is_null($existingKey)) {
+ $this->log($output, "Existing issue {$existingKey} covers {$issue->uniqueId()}.");
+ } elseif (!$input->getOption('dry-run')) {
+ $key = $issue->ensure();
+ $this->log($output, "Created issue {$key} for {$issue->uniqueId()}.");
+ } else {
+ $this->log($output, "Would have created an issue for {$issue->uniqueId()} if not a dry run.");
}
- if ($key) {
- $this->logLine($output, "Existing issue found: {$key}.");
+
+ $alertsFound[] = $issue->uniqueId();
+ }
+
+ $pull_requests = $this->fetchPullRequestData();
+
+ foreach ($pull_requests as $pull_request) {
+ $issue = new PullRequestIssue($pull_request['node']);
+
+ if (\in_array($issue->uniqueId(), $alertsFound)) {
continue;
}
- // Create the Jira issue.
- if (empty($input->getOption('dry-run'))) {
- $key = $issue->create();
+ $existingKey = $issue->exists();
- // Issue creation failed. Bail out.
- if (empty($key)) {
- $this->logLine($output, 'ERROR CREATING ISSUE.');
- exit(1);
- }
- $this->logLine($output, "Created issue {$key}");
+ if (!\is_null($existingKey)) {
+ $this->log($output, "Existing issue {$existingKey} covers {$issue->uniqueId()}.");
+ } elseif (!$input->getOption('dry-run')) {
+ $key = $issue->ensure();
+ $this->log($output, "Created issue {$key} for {$issue->uniqueId()}.");
} else {
- $this->logLine($output, "Would have created an issue in {$jira_project} if not a dry run.");
+ $this->log($output, "Would have created an issue for {$issue->uniqueId()} if not a dry run.");
}
}
+
+ return 0;
}
/**
+ * phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
+ *
* Fetch alert data from GitHub.
+ *
+ * @return array
*/
- protected function fetchAlertData()
+ protected function fetchAlertData(): array
{
+ // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
$query = <<<'GQL'
query alerts($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
@@ -207,81 +182,123 @@ protected function fetchAlertData()
}
GQL;
- $repo = explode('/', getenv('GITHUB_REPOSITORY'));
+ $repo = \explode('/', \getenv('GITHUB_REPOSITORY') ?: '');
$variables = [
'owner' => $repo[0],
'repo' => $repo[1],
];
$response = $this->getGHClient()->query($query, $variables);
+
if ($response->hasErrors()) {
- $messages = array_map(function (array $error) {
+ $messages = \array_map(static function (array $error) {
return $error['message'];
}, $response->getErrors());
- throw new \RuntimeException(
- sprintf('GraphQL client error: %s. Original query: %s', implode(', ', $messages), $query)
+ throw new RuntimeException(
+ \sprintf('GraphQL client error: %s. Original query: %s', \implode(', ', $messages), $query),
);
}
// Drill down to the response data we want, if there.
$alert_data = $response->getData();
- $alerts = $alert_data['repository']['vulnerabilityAlerts']['nodes'] ?? [];
- return $alerts;
+ return $alert_data['repository']['vulnerabilityAlerts']['nodes'] ?? [];
}
/**
- * Create the GraphQL client with supplied Bearer token.
+ * Fetch Dependabot pull request data from GitHub.
*
+ * @return array>>
+ */
+ protected function fetchPullRequestData(): array
+ {
+ $repo = \getenv('GITHUB_REPOSITORY');
+ $author = 'author:app/dependabot author:app/dependabot-preview';
+
+ $query = <<getGHClient()->query($query, $variables);
+
+ if ($response->hasErrors()) {
+ $messages = \array_map(static function (array $error) {
+ return $error['message'];
+ }, $response->getErrors());
+
+ throw new RuntimeException(
+ \sprintf('GraphQL client error: %s. Original query: %s', \implode(', ', $messages), $query),
+ );
+ }
+
+ // Drill down to the response data we want, if there.
+ $pr_data = $response->getData();
+
+ return $pr_data['search']['edges'] ?? [];
+ }
+
+ /**
+ * Create the GraphQL client with supplied Bearer token.
*/
- protected function getGHClient()
+ protected function getGHClient(): GraphQLClient
{
+ $access_token = \getenv('GH_SECURITY_TOKEN');
- $access_token = getenv('GH_SECURITY_TOKEN');
- $client = ClientBuilder::build('https://api.github.com/graphql', [
+ return ClientBuilder::build('https://api.github.com/graphql', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => "Bearer {$access_token}",
],
]);
-
- return $client;
}
/**
* Validate the required options.
*/
- protected function validateConfig()
+ protected function validateConfig(): void
{
foreach ($this->requiredOptions as $option) {
- $var = getenv($option);
- if ($var === false || empty($var)) {
- throw new \RuntimeException("Required env variable '{$option}' not set or empty.");
+ $var = \getenv($option);
+
+ if (!\is_string($var)) {
+ throw new RuntimeException("Required env variable '{$option}' not set or empty.");
}
- if ($option == 'GITHUB_REPOSITORY') {
- if (count(explode('/', $var)) < 2) {
- throw new \RuntimeException('GitHub repository invalid: ' . getenv('GITHUB_REPOSITORY'));
- }
+
+ if (($option === 'GITHUB_REPOSITORY') && (\count(\explode('/', $var)) < 2)) {
+ throw new RuntimeException('GitHub repository invalid: ' . \getenv('GITHUB_REPOSITORY'));
}
}
}
- protected function log(OutputInterface $output, string $message)
+ protected function log(OutputInterface $output, string $message): void
{
if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
return;
}
- $output->write($message);
- }
-
- protected function logLine(OutputInterface $output, string $message)
- {
- if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
- return;
- }
+ $timestamp = \gmdate(\DATE_ISO8601);
+ $jira_project = \getenv('JIRA_PROJECT');
- $output->writeln($message);
+ $output->writeln("{$timestamp} - {$jira_project} - {$message}");
}
}