Completed
Pull Request — master (#1)
by Marco
02:07
created

Verify::verify()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 19
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\ComposerGpgVerify;
6
7
use Composer\Composer;
8
use Composer\Config;
9
use Composer\EventDispatcher\EventSubscriberInterface;
10
use Composer\Installer\InstallationManager;
11
use Composer\IO\IOInterface;
12
use Composer\Package\PackageInterface;
13
use Composer\Plugin\PluginInterface;
14
use Composer\Script\Event;
15
use Composer\Script\ScriptEvents;
16
use Roave\ComposerGpgVerify\Exception\PackagesTrustCheckFailed;
17
use Roave\ComposerGpgVerify\Package\Git\GitSignatureCheck;
18
use Roave\ComposerGpgVerify\Package\GitPackage;
19
use Roave\ComposerGpgVerify\Package\PackageVerification;
20
use Roave\ComposerGpgVerify\Package\UnknownPackageFormat;
21
22
final class Verify implements PluginInterface, EventSubscriberInterface
23
{
24
    /**
25
     * @codeCoverageIgnore
26
     */
27
    private function __construct()
28
    {
29
    }
30
31
    /**
32
     * {@inheritDoc}
33
     */
34
    public static function getSubscribedEvents() : array
35
    {
36
        return [
37
            ScriptEvents::PRE_AUTOLOAD_DUMP => 'verify',
38
        ];
39
    }
40
41
    /**
42
     * {@inheritDoc}
43
     *
44
     * @codeCoverageIgnore
45
     */
46
    public function activate(Composer $composer, IOInterface $io) : void
47
    {
48
        // Nothing to do here, as all features are provided through event listeners
49
    }
50
51
    /**
52
     * @param Event $composerEvent
53
     *
54
     * @throws \Roave\ComposerGpgVerify\Exception\PackagesTrustCheckFailed
55
     * @throws \LogicException
56
     */
57
    public static function verify(Event $composerEvent) : void
58
    {
59
        $originalLanguage = getenv('LANGUAGE');
60
        $composer         = $composerEvent->getComposer();
61
        $config           = $composer->getConfig();
62
63
        self::assertSourceInstallation($config);
64
65
        // prevent output changes caused by locale settings on the system where this script is running
66
        putenv(sprintf('LANGUAGE=%s', 'en_US'));
67
68
        $installationManager = $composer->getInstallationManager();
69
        /* @var $checkedPackages PackageVerification[] */
70
        $checkedPackages     = array_map(
71
            function (PackageInterface $package) use ($installationManager) : PackageVerification {
72
                return self::verifyPackage($installationManager, $package);
73
            },
74
            $composer->getRepositoryManager()->getLocalRepository()->getPackages()
75
        );
76
77
        putenv(sprintf('LANGUAGE=%s', (string) $originalLanguage));
78
79
        $escapes = array_filter(
80
            $checkedPackages,
81
            function (PackageVerification $verification) : bool {
82
                return ! $verification->isVerified();
83
            }
84
        );
85
86
        if (! $escapes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $escapes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
87
            return;
88
        }
89
90
        throw PackagesTrustCheckFailed::fromFailedPackageVerifications(...$escapes);
91
    }
92
93
    private static function verifyPackage(
94
        InstallationManager $installationManager,
95
        PackageInterface $package
96
    ) : PackageVerification {
97
        $gitDirectory = $installationManager->getInstallPath($package) . '/.git';
98
99
        if (! is_dir($gitDirectory)) {
100
            return UnknownPackageFormat::fromNonGitPackage($package);
101
        }
102
103
        return GitPackage::fromPackageAndSignatureChecks(
104
            $package,
105
            self::checkCurrentCommitSignature($gitDirectory, $package),
106
            ...self::checkTagSignatures($gitDirectory, $package, ...self::getTagsForCurrentCommit($gitDirectory))
107
        );
108
    }
109
110 View Code Duplication
    private static function checkCurrentCommitSignature(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
        string $gitDirectory,
112
        PackageInterface $package
113
    ) : GitSignatureCheck {
114
        $command = sprintf(
115
            'git --git-dir %s verify-commit --verbose HEAD 2>&1',
116
            escapeshellarg($gitDirectory)
117
        );
118
119
        exec($command, $output, $exitCode);
120
121
        return GitSignatureCheck::fromGitCommitCheck($package, $command, $exitCode, implode("\n", $output));
122
    }
123
124
    /**
125
     * @param string $gitDirectory
126
     *
127
     * @return string[]
128
     */
129
    private static function getTagsForCurrentCommit(string $gitDirectory) : array
130
    {
131
        exec(
132
            sprintf(
133
                'git --git-dir %s tag --points-at HEAD 2>&1',
134
                escapeshellarg($gitDirectory)
135
            ),
136
            $tags
137
        );
138
139
        return array_values(array_filter($tags));
140
    }
141
142
143
    /**
144
     * @param string           $gitDirectory
145
     * @param PackageInterface $package
146
     * @param string[]         $tags
147
     *
148
     * @return GitSignatureCheck[]
149
     */
150
    private static function checkTagSignatures(string $gitDirectory, PackageInterface $package, string ...$tags) : array
151
    {
152
        return array_map(
153 View Code Duplication
            function (string $tag) use ($gitDirectory, $package) : GitSignatureCheck {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
                $command = sprintf(
155
                    'git --git-dir %s tag -v %s 2>&1',
156
                    escapeshellarg($gitDirectory),
157
                    escapeshellarg($tag)
158
                );
159
160
                exec($command, $tagSignatureOutput, $exitCode);
161
162
                return GitSignatureCheck::fromGitTagCheck(
163
                    $package,
164
                    $command,
165
                    $exitCode,
166
                    implode("\n", $tagSignatureOutput)
167
                );
168
            },
169
            $tags
170
        );
171
    }
172
173
    /**
174
     * @param Config $config
175
     *
176
     *
177
     * @throws \LogicException
178
     * @throws \RuntimeException
179
     */
180
    private static function assertSourceInstallation(Config $config) : void
181
    {
182
        $preferredInstall = $config->get('preferred-install');
183
184
        if ('source' !== $preferredInstall) {
185
            throw new \LogicException(sprintf(
186
                'Expected installation "preferred-install" to be "source", found "%s" instead',
187
                (string) $preferredInstall
188
            ));
189
        }
190
    }
191
}
192