Completed
Pull Request — master (#37)
by
unknown
02:44
created

SecurityAlertCheckTask::discernIdentifier()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace BringYourOwnIdeas\SecurityChecker\Tasks;
4
5
use SensioLabs\Security\SecurityChecker;
6
use BringYourOwnIdeas\SecurityChecker\Models\SecurityAlert;
7
use BringYourOwnIdeas\SecurityChecker\Extensions\SecurityAlertExtension;
8
use BringYourOwnIdeas\Maintenance\Model\Package;
0 ignored issues
show
Bug introduced by
The type BringYourOwnIdeas\Maintenance\Model\Package was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use SilverStripe\ORM\Queries\SQLDelete;
10
use SilverStripe\ORM\DataObjectSchema;
11
use SilverStripe\Dev\SapphireTest;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Dev\BuildTask;
14
15
/**
16
 * Checks if there are any insecure dependencies.
17
 */
18
class SecurityAlertCheckTask extends BuildTask
19
{
20
    private static $segment = 'SecurityAlertCheckTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
21
22
    /**
23
     * @var SecurityChecker
24
     */
25
    protected $securityChecker;
26
27
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
28
        'SecurityChecker' => '%$' . SecurityChecker::class,
29
    ];
30
31
    protected $title = 'Composer security checker';
32
33
    protected $description =
34
        'Checks if any modules managed through composer have known security vulnerabilities at the used version.';
35
36
    /**
37
     * @return SecurityChecker
38
     */
39
    public function getSecurityChecker()
40
    {
41
        return $this->securityChecker;
42
    }
43
44
    /**
45
     * @param SecurityChecker $securityChecker
46
     * @return $this
47
     */
48
    public function setSecurityChecker(SecurityChecker $securityChecker)
49
    {
50
        $this->securityChecker = $securityChecker;
51
        return $this;
52
    }
53
54
    /**
55
     * Most SilverStripe issued alerts are _not_ assiged CVEs.
56
     * However they have their own identifier in the form of a
57
     * prefix to the title - we can use this instead of a CVE ID.
58
     *
59
     * @param string $cve
60
     * @param string $title
61
     *
62
     * @return string
63
     */
64
    protected function discernIdentifier($cve, $title)
65
    {
66
        $identifier = $cve;
67
        if (!$identifier || $identifier === '~') {
68
            $identifier = explode(':', $title);
69
            $identifier = array_shift($identifier);
70
        }
71
        $this->extend('updateIdentifier', $identifier, $cve, $title);
72
        return $identifier;
73
    }
74
75
    public function run($request)
76
    {
77
        // to keep the list up to date while removing resolved issues we keep all of found issues
78
        $validEntries = array();
79
80
        // use the security checker of
81
        $checker = $this->getSecurityChecker();
82
        $alerts = $checker->check(BASE_PATH . DIRECTORY_SEPARATOR . 'composer.lock');
83
84
        // go through all alerts for packages - each can contain multiple issues
85
        foreach ($alerts as $package => $packageDetails) {
86
            // go through each individual known security issue
87
            foreach ($packageDetails['advisories'] as $details) {
88
                $identifier = $this->discernIdentifier($details['cve'], $details['title']);
89
                // check if this vulnerability is already known
90
                $vulnerability = SecurityAlert::get()->filter(array(
91
                    'PackageName' => $package,
92
                    'Version' => $packageDetails['version'],
93
                    'Identifier'   => $identifier,
94
                ));
95
96
                // Is this vulnerability known? No, lets add it.
97
                if ((int) $vulnerability->count() === 0) {
98
                    $vulnerability = SecurityAlert::create();
99
                    $vulnerability->PackageName  = $package;
100
                    $vulnerability->Version      = $packageDetails['version'];
101
                    $vulnerability->Title        = $details['title'];
102
                    $vulnerability->ExternalLink = $details['link'];
103
                    $vulnerability->Identifier   = $identifier;
104
105
                    $vulnerability->write();
106
107
                    // add the new entries to the list of valid entries
108
                    $validEntries[] = $vulnerability->ID;
109
                } else {
110
                    // add existing vulnerabilities (probably just 1) to the list of valid entries
111
                    $validEntries = array_merge($validEntries, $vulnerability->column('ID'));
112
                }
113
114
                // Relate this vulnerability to an existing Package, if the
115
                // bringyourownideas/silverstripe-maintenance module is installed
116
                if ($vulnerability->hasExtension(SecurityAlertExtension::class)
117
                    && class_exists(Package::class)
118
                    && $vulnerability->PackageRecordID === 0
119
                    && $packageRecord = Package::get()->find('Name', $package)
120
                ) {
121
                    $vulnerability->PackageRecordID = $packageRecord->ID;
122
                }
123
            }
124
        }
125
126
        // remove all entries which are resolved (no longer $validEntries)
127
        $tableName = DataObjectSchema::create()->tableName(SecurityAlert::class);
128
        $removeOldSecurityAlerts = SQLDelete::create("\"$tableName\"");
129
        if (empty($validEntries)) {
130
            // There were no SecurityAlerts listed for our installation - so flush any old data
131
            $removeOldSecurityAlerts->execute();
132
        } else {
133
            $removable = SecurityAlert::get()->exclude(array('ID' => $validEntries));
134
            // Be careful not to remove all SecurityAlerts on the case that every entry is valid
135
            if ($removable->exists()) {
136
                // SQLConditionalExpression does not support IN() syntax via addWhere
137
                // so we have to build this up manually
138
                $convertIDsToQuestionMarks = function ($id) {
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

138
                $convertIDsToQuestionMarks = function (/** @scrutinizer ignore-unused */ $id) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
139
                    return '?';
140
                };
141
                $queryArgs = $removable->column('ID');
142
                $paramPlaceholders = implode(',', array_map($convertIDsToQuestionMarks, $queryArgs));
143
144
                $removeOldSecurityAlerts = $removeOldSecurityAlerts->addWhere([
145
                    '"ID" IN(' . $paramPlaceholders . ')' => $queryArgs
146
                ]);
147
                $removeOldSecurityAlerts->execute();
148
            }
149
        }
150
151
        // notify that the task finished.
152
        $this->output('The task finished running. You can find the updated information in the database now.');
153
    }
154
155
    /**
156
     * prints a message during the run of the task
157
     *
158
     * @param string $text
159
     */
160
    protected function output($text)
161
    {
162
        echo Director::is_cli() ? $text . PHP_EOL : "<p>$text</p>\n";
163
    }
164
}
165