Completed
Push — master ( da1a6c...6bb072 )
by Marco
02:08
created

GitSignatureCheck::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 1
eloc 21
nc 1
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\ComposerGpgVerify\Package\Git;
6
7
use Composer\Package\PackageInterface;
8
9
/**
10
 * @internal do not use: I will cut you.
11
 *
12
 * This class abstracts the parsing of the output of `git verify-commit` and `git tag -v`, which are broken/useless.
13
 * Ideally, we'd massively simplify this class once GIT 2.12 is mainstream
14
 *
15
 * @link https://stackoverflow.com/questions/17371955/verifying-signed-git-commits/32038784#32038784
16
 */
17
class GitSignatureCheck
18
{
19
    /**
20
     * @var string
21
     */
22
    private $packageName;
23
24
    /**
25
     * @var string
26
     */
27
    private $command;
28
29
    /**
30
     * @var string|null
31
     */
32
    private $commitHash;
33
34
    /**
35
     * @var string|null
36
     */
37
    private $tagName;
38
39
    /**
40
     * @var int
41
     */
42
    private $exitCode;
43
44
    /**
45
     * @var string
46
     */
47
    private $output;
48
49
    /**
50
     * @var bool
51
     */
52
    private $isSigned;
53
54
    /**
55
     * @var bool
56
     */
57
    private $isVerified;
58
59
    /**
60
     * @var string|null
61
     */
62
    private $signatureAuthor;
63
64
    /**
65
     * @var string|null
66
     */
67
    private $signatureKey;
68
69
    private function __construct(
70
        string $packageName,
71
        ?string $commitHash,
72
        ?string $tagName,
73
        string $command,
74
        int $exitCode,
75
        string $output,
76
        bool $isSigned,
77
        bool $isVerified,
78
        ?string $signatureAuthor,
79
        ?string $signatureKey
80
    ) {
81
        $this->packageName     = $packageName; // @TODO get rid of this, or add it to the error messages
82
        $this->commitHash      = $commitHash;
83
        $this->tagName         = $tagName;
84
        $this->command         = $command;
85
        $this->exitCode        = $exitCode;
86
        $this->output          = $output;
87
        $this->isSigned        = $isSigned;
88
        $this->isVerified      = $isVerified;
89
        $this->signatureAuthor = $signatureAuthor;
90
        $this->signatureKey    = $signatureKey;
91
    }
92
93
    public static function fromGitCommitCheck(
94
        PackageInterface $package,
95
        string $command,
96
        int $exitCode,
97
        string $output
98
    ) : self {
99
        $signatureKey = self::extractKeyIdentifier($output);
100
        $signed       = $signatureKey && ! $exitCode;
101
102
        return new self(
103
            $package->getName(),
104
            self::extractCommitHash($output),
105
            null,
106
            $command,
107
            $exitCode,
108
            $output,
109
            $signed,
110
            $signed && self::signatureValidationHasNoWarnings($output),
111
            self::extractSignatureAuthor($output),
112
            $signatureKey
113
        );
114
    }
115
116
    public static function fromGitTagCheck(
117
        PackageInterface $package,
118
        string $command,
119
        int $exitCode,
120
        string $output
121
    ) : self {
122
        $signatureKey = self::extractKeyIdentifier($output);
123
        $signed       = $signatureKey && ! $exitCode;
124
125
        return new self(
126
            $package->getName(),
127
            self::extractCommitHash($output),
128
            self::extractTagName($output),
129
            $command,
130
            $exitCode,
131
            $output,
132
            $signed,
133
            $signed && self::signatureValidationHasNoWarnings($output),
134
            self::extractSignatureAuthor($output),
135
            self::extractKeyIdentifier($output)
136
        );
137
    }
138
139
    public function asHumanReadableString() : string
140
    {
141
        return implode(
142
            "\n",
143
            [
144
                (($this->isSigned || $this->signatureKey) ? '[SIGNED]' : '[NOT SIGNED]')
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->signatureKey of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
145
                . ' ' . ($this->isVerified ? '[VERIFIED]' : '[NOT VERIFIED]')
146
                . ' ' . ($this->commitHash ? 'Commit #' . $this->commitHash : '')
147
                . ' ' . ($this->tagName ? 'Tag ' . $this->tagName : '')
148
                . ' ' . ($this->signatureAuthor ? 'By "' . $this->signatureAuthor . '"' : '')
149
                . ' ' . ($this->signatureKey ? '(Key ' . $this->signatureKey . ')' : ''),
150
                'Command: ' . $this->command,
151
                'Exit code: ' . $this->exitCode,
152
                'Output: ' . $this->output,
153
            ]
154
        );
155
    }
156
157
    public function canBeTrusted() : bool
158
    {
159
        return $this->isVerified;
160
    }
161
162
    private static function extractCommitHash(string $output) : ?string
163
    {
164
        $keys = array_filter(array_map(
165
            function (string $outputRow) {
166
                preg_match('/^(tree|object) ([a-fA-F0-9]{40})$/i', $outputRow, $matches);
167
168
                return $matches[2] ?? false;
169
            },
170
            explode("\n", $output)
171
        ));
172
173
        return reset($keys) ?: null;
174
    }
175
176
    private static function extractTagName(string $output) : ?string
177
    {
178
        $keys = array_filter(array_map(
179
            function (string $outputRow) {
180
                preg_match('/^tag (.+)$/i', $outputRow, $matches);
181
182
                return $matches[1] ?? false;
183
            },
184
            explode("\n", $output)
185
        ));
186
187
        return reset($keys) ?: null;
188
    }
189
190
    private static function extractKeyIdentifier(string $output) : ?string
191
    {
192
        $keys = array_filter(array_map(
193
            function (string $outputRow) {
194
                preg_match('/gpg:.*using .* key ([a-fA-F0-9]+)/i', $outputRow, $matches);
195
196
                return $matches[1] ?? false;
197
            },
198
            explode("\n", $output)
199
        ));
200
201
        return reset($keys) ?: null;
202
    }
203
204
    private static function extractSignatureAuthor(string $output) : ?string
205
    {
206
        $keys = array_filter(array_map(
207
            function (string $outputRow) {
208
                preg_match('/gpg: Good signature from "(.+)" \\[.*\\]/i', $outputRow, $matches);
209
210
                return $matches[1] ?? false;
211
            },
212
            explode("\n", $output)
213
        ));
214
215
        return reset($keys) ?: null;
216
    }
217
218
    private static function signatureValidationHasNoWarnings(string $output) : bool
219
    {
220
        return ! array_filter(
221
            explode("\n", $output),
222
            function (string $outputRow) {
223
                return false !== strpos($outputRow, 'gpg: WARNING: ');
224
            }
225
        );
226
    }
227
}
228