Completed
Pull Request — master (#1)
by Marco
06:36 queued 01:57
created

Verify::verify()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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