Completed
Pull Request — master (#1)
by Marco
02:14
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\Exception\PreferredInstallIsNotSource;
18
use Roave\ComposerGpgVerify\Package\Git\GitSignatureCheck;
19
use Roave\ComposerGpgVerify\Package\GitPackage;
20
use Roave\ComposerGpgVerify\Package\PackageVerification;
21
use Roave\ComposerGpgVerify\Package\UnknownPackageFormat;
22
23
final class Verify implements PluginInterface, EventSubscriberInterface
24
{
25
    /**
26
     * {@inheritDoc}
27
     */
28
    public static function getSubscribedEvents() : array
29
    {
30
        return [
31
            ScriptEvents::PRE_AUTOLOAD_DUMP => 'verify',
32
        ];
33
    }
34
35
    /**
36
     * {@inheritDoc}
37
     *
38
     * @codeCoverageIgnore
39
     */
40
    public function activate(Composer $composer, IOInterface $io) : void
41
    {
42
        // Nothing to do here, as all features are provided through event listeners
43
    }
44
45
    /**
46
     * @param Event $composerEvent
47
     *
48
     * @throws \Roave\ComposerGpgVerify\Exception\PackagesTrustCheckFailed
49
     * @throws \RuntimeException
50
     * @throws \Roave\ComposerGpgVerify\Exception\PreferredInstallIsNotSource
51
     */
52
    public static function verify(Event $composerEvent) : void
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
53
    {
54
        $originalLanguage = getenv('LANGUAGE');
55
        $composer         = $composerEvent->getComposer();
56
        $config           = $composer->getConfig();
57
58
        self::assertSourceInstallation($config);
59
60
        // prevent output changes caused by locale settings on the system where this script is running
61
        putenv(sprintf('LANGUAGE=%s', 'en_US'));
62
63
        $installationManager = $composer->getInstallationManager();
64
        /* @var $checkedPackages PackageVerification[] */
65
        $checkedPackages     = array_map(
66
            function (PackageInterface $package) use ($installationManager) : PackageVerification {
67
                return self::verifyPackage($installationManager, $package);
68
            },
69
            $composer->getRepositoryManager()->getLocalRepository()->getPackages()
70
        );
71
72
        putenv(sprintf('LANGUAGE=%s', (string) $originalLanguage));
73
74
        $escapes = array_filter(
75
            $checkedPackages,
76
            function (PackageVerification $verification) : bool {
77
                return ! $verification->isVerified();
78
            }
79
        );
80
81
        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...
82
            return;
83
        }
84
85
        throw PackagesTrustCheckFailed::fromFailedPackageVerifications(...$escapes);
86
    }
87
88
    private static function verifyPackage(
89
        InstallationManager $installationManager,
90
        PackageInterface $package
91
    ) : PackageVerification {
92
        $gitDirectory = $installationManager->getInstallPath($package) . '/.git';
93
94
        if (! is_dir($gitDirectory)) {
95
            return UnknownPackageFormat::fromNonGitPackage($package);
96
        }
97
98
        return GitPackage::fromPackageAndSignatureChecks(
99
            $package,
100
            self::checkCurrentCommitSignature($gitDirectory, $package),
101
            ...self::checkTagSignatures($gitDirectory, $package, ...self::getTagsForCurrentCommit($gitDirectory))
102
        );
103
    }
104
105 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...
106
        string $gitDirectory,
107
        PackageInterface $package
108
    ) : GitSignatureCheck {
109
        $command = sprintf(
110
            'git --git-dir %s verify-commit --verbose HEAD 2>&1',
111
            escapeshellarg($gitDirectory)
112
        );
113
114
        exec($command, $output, $exitCode);
115
116
        return GitSignatureCheck::fromGitCommitCheck($package, $command, $exitCode, implode("\n", $output));
117
    }
118
119
    /**
120
     * @param string $gitDirectory
121
     *
122
     * @return string[]
123
     */
124
    private static function getTagsForCurrentCommit(string $gitDirectory) : array
125
    {
126
        exec(
127
            sprintf(
128
                'git --git-dir %s tag --points-at HEAD 2>&1',
129
                escapeshellarg($gitDirectory)
130
            ),
131
            $tags
132
        );
133
134
        return array_values(array_filter($tags));
135
    }
136
137
138
    /**
139
     * @param string           $gitDirectory
140
     * @param PackageInterface $package
141
     * @param string[]         $tags
142
     *
143
     * @return GitSignatureCheck[]
144
     */
145
    private static function checkTagSignatures(string $gitDirectory, PackageInterface $package, string ...$tags) : array
146
    {
147
        return array_map(
148 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...
149
                $command = sprintf(
150
                    'git --git-dir %s tag -v %s 2>&1',
151
                    escapeshellarg($gitDirectory),
152
                    escapeshellarg($tag)
153
                );
154
155
                exec($command, $tagSignatureOutput, $exitCode);
156
157
                return GitSignatureCheck::fromGitTagCheck(
158
                    $package,
159
                    $command,
160
                    $exitCode,
161
                    implode("\n", $tagSignatureOutput)
162
                );
163
            },
164
            $tags
165
        );
166
    }
167
168
    /**
169
     * @param Config $config
170
     *
171
     *
172
     * @throws \RuntimeException
173
     * @throws \Roave\ComposerGpgVerify\Exception\PreferredInstallIsNotSource
174
     */
175
    private static function assertSourceInstallation(Config $config) : void
176
    {
177
        $preferredInstall = $config->get('preferred-install');
178
179
        if ('source' !== $preferredInstall) {
180
            throw PreferredInstallIsNotSource::fromPreferredInstall($preferredInstall);
181
        }
182
    }
183
}
184