GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

SecureShell   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 330
Duplicated Lines 11.52 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 43
lcom 2
cbo 4
dl 38
loc 330
rs 8.96
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A _initialize() 0 16 6
A openConnection() 0 27 5
A closeConnection() 0 4 1
A getConnection() 0 3 1
A __authenticate() 0 17 6
B __checkFingerprint() 0 30 8
A runRemoteCommand() 0 13 2
A seeInRemoteOutput() 0 4 1
A dontSeeInRemoteOutput() 0 4 1
A seeRemoteFile() 10 10 2
A dontSeeRemoteFile() 10 10 2
A grabRemoteFile() 0 9 2
A seeRemoteDir() 9 9 2
A dontSeeRemoteDir() 9 9 2
A grabRemoteDir() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SecureShell often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SecureShell, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Codeception\Extension;
3
4
use Codeception\Exception\ModuleException;
5
use Codeception\Exception\ModuleConfigException;
6
use Codeception\Module;
7
use Codeception\Configuration;
8
use \SplFileObject;
9
use \RuntimeException;
10
use \Exception;
11
12
class SecureShell extends Module
13
{
14
15
    const DEFAULT_PORT  = 22;
16
    const AUTH_PASSWORD = 1;
17
    const AUTH_PUBKEY   = 2;
18
    const AUTH_HOSTKEY  = 3;
19
    const AUTH_AGENT    = 4;
20
    const AUTH_NONE     = 0;
21
22
    protected $config = ['StrictHostKeyChecking', 'KnownHostsFile'];
23
24
    protected $requiredFields = [];
25
26
    /**
27
     * @var string $knownHostsFile
28
     */
29
    protected static $knownHostsFile = '';
30
31
    /**
32
     * @var bool $strictHostKeyChecking
33
     */
34
    protected static $strictHostKeyChecking = false;
35
36
    /**
37
     * @var mixed|null $connection
38
     */
39
    protected $connection;
40
41
    /**
42
     * @var string[] $output
43
     */
44
    private $output;
45
46
    /**
47
     * @codeCoverageIgnore
48
     * @ignore Codeception specific
49
     */
50
    public function _initialize()
51
    {
52
        if (isset($this->config['StrictHostKeyChecking'])) {
53
            static::$strictHostKeyChecking = (bool) $this->config['StrictHostKeyChecking'];
54
        }
55
        if (isset($this->config['KnownHostsFile'])) {
56
            static::$knownHostsFile = $this->config['KnownHostsFile'];
57
        } elseif (static::$strictHostKeyChecking) {
58
            // default KnownHostsFile if StrictHostKeyChecking enabled
59
            static::$knownHostsFile = Configuration::projectDir().'known_hosts';
60
        }
61
        // check that a KnownHostsFile exists if StrictHostKeyChecking enabled
62
        if (static::$strictHostKeyChecking && !file_exists(static::$knownHostsFile)) {
63
            throw new ModuleConfigException(__CLASS__, 'KnownHostsFile "'.static::$knownHostsFile.'" not found');
64
        }
65
    }
66
67
    /**
68
     * Open an SSH connection to the host
69
     *
70
     * @param string $host
71
     * @param int $port
72
     * @param int $auth
73
     * @param mixed ...$args
74
     *
75
     * @return resource|null Returns the SSH connection
76
     */
77
    public function openConnection($host,
78
                                    $port = SecureShell::DEFAULT_PORT,
79
                                    $auth = SecureShell::AUTH_PASSWORD,
80
                                    ...$args)
81
    {
82
        $callbacks = array('disconnect' => [$this, '_disconnect']);
83
84
        try {
85
            $connection = ssh2_connect($host, $port, $callbacks);
86
            if (!$connection) {
87
                throw new ModuleException(__CLASS__, "Unable to connect to {$host} on port {$port}");
88
            } else {
89
                $this->__checkFingerprint($connection);
90
91
                if ($this->__authenticate($connection, $auth, ...$args) === false) {
92
                    throw new ModuleException(__CLASS__, "Authentication failed on server {$host}:{$port}");
93
                } else {
94
                    $this->connection = $connection;
95
                }
96
            }
97
        } catch (ModuleException $e) {
98
            throw $e;
99
        } catch (Exception $e) {
100
            throw new ModuleException(__CLASS__, $e->getMessage());
101
        }
102
        return $this->connection;
103
    }
104
105
    /**
106
     * Close current SSH connection
107
     *
108
     * @return boolean Return true once executed
109
     */
110
    public function closeConnection() {
111
        $this->connection = null;
112
        return true;
113
    }
114
115
    /**
116
     * Get current SSH connection
117
     *
118
     * @return resource|null Return current SSH connection
119
     */
120
    public function getConnection() {
121
        return $this->connection;
122
    }
123
124
    /**
125
     * Run SSH authentication based on the selected method
126
     *
127
     * @param resource $connection
128
     * @param int $method
129
     * @param mixed ...$args
130
     *
131
     * @return void
132
     */
133
    protected function __authenticate($connection, $method, ...$args)
134
    {
135
        switch ($method) {
136
            case SecureShell::AUTH_PASSWORD:
137
                return ssh2_auth_password($connection, ...$args);
138
            case SecureShell::AUTH_PUBKEY:
139
                return ssh2_auth_pubkey_file($connection, ...$args);
140
            case SecureShell::AUTH_HOSTKEY:
141
                return ssh2_auth_hostbased_file($connection, ...$args);
142
            case SecureShell::AUTH_AGENT:
143
                return ssh2_auth_agent($connection, ...$args);
144
            case SecureShell::AUTH_NONE:
145
                return ssh2_auth_none($connection, ...$args);
146
            default:
147
                throw new ModuleException(__CLASS__, 'Unsupported authentication method');
148
        }
149
    }
150
151
    /**
152
     * Check if the SSH server public key fingerprint is valid
153
     *
154
     * @param resource $connection
155
     *
156
     * @return string Server public key fingerprint
157
     */
158
    protected function __checkFingerprint($connection)
159
    {
160
        $knownHost = false;
161
        try {
162
            $fingerprint = ssh2_fingerprint($connection, SSH2_FINGERPRINT_MD5);
163
            if (file_exists(static::$knownHostsFile)) {
164
                $file = new SplFileObject(static::$knownHostsFile);
165
                $file->setFlags(SplFileObject::READ_CSV);
166
                $file->setCsvControl(' ');
167
                foreach ($file as $entry) {
168
                    list(,, $fp) = $entry;
169
                    $fp = md5(base64_decode($fp));
170
                    $knownHost = (strcasecmp($fp, $fingerprint) === 0);
171
                    if ($knownHost) {
172
                        break;
173
                    }
174
                }
175
            }
176
            $knownHost = $knownHost || !static::$strictHostKeyChecking;
177
178
            if ($knownHost === false) {
179
                throw new ModuleException(__CLASS__, 'Unable to verify server identity!');
180
            }
181
        } catch (RuntimeException $e) {
182
            if (static::$strictHostKeyChecking) {
183
                throw new ModuleException(__CLASS__, 'Unable to verify server identity!');
184
            }
185
        }
186
        return $fingerprint;
187
    }
188
189
    /**
190
     * Run a command on the remote host and return the output
191
     *
192
     * @param string $command
193
     *
194
     * @return string[] Return command output 'STDOUT' and 'STDERR'
195
     */
196
    public function runRemoteCommand($command)
197
    {
198
        try {
199
            $stream = ssh2_exec($this->connection, $command);
200
            stream_set_blocking($stream, true);
201
            $errStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
202
            $this->output['STDOUT'] = stream_get_contents($stream);
203
            $this->output['STDERR'] = stream_get_contents($errStream);
204
            return $this->output;
205
        } catch (Exception $e) {
206
            throw new ModuleException(__CLASS__, $e->getMessage());
207
        }
208
    }
209
210
    /**
211
     * Verify that the last remote command output contains a specific text
212
     *
213
     * @param string $text
214
     *
215
     * @return void
216
     */
217
    public function seeInRemoteOutput($text)
218
    {
219
        \PHPUnit_Framework_Assert::assertContains($text, $this->output['STDOUT']);
220
    }
221
222
    /**
223
     * Verify that the last remote command output does not contain a specific text
224
     *
225
     * @param string $text
226
     *
227
     * @return void
228
     */
229
    public function dontSeeInRemoteOutput($text)
230
    {
231
        \PHPUnit_Framework_Assert::assertNotContains($text, $this->output['STDOUT']);
232
    }
233
234
235
    /**
236
     * Verify that a file exists in the current remote folder
237
     *
238
     * @param string $filename
239
     *
240
     * @return void
241
     */
242 View Code Duplication
    public function seeRemoteFile($filename)
243
    {
244
        $sftp = ssh2_sftp($this->connection);
245
        try {
246
            $res = ssh2_sftp_stat($sftp, $filename);
247
        } catch (Exception $e) {
248
            $res = null;
249
        }
250
        \PHPUnit_Framework_Assert::assertNotEmpty($res);
251
    }
252
253
    /**
254
     * Verify that a file does not exist in the current remote folder
255
     *
256
     * @param string $filename
257
     *
258
     * @return void
259
     */
260 View Code Duplication
    public function dontSeeRemoteFile($filename)
261
    {
262
        $sftp = ssh2_sftp($this->connection);
263
        try {
264
            $res = (bool) ssh2_sftp_stat($sftp, $filename);
265
        } catch (Exception $e) {
266
            $res = false;
267
        }
268
        \PHPUnit_Framework_Assert::assertFalse($res);
269
    }
270
271
    /**
272
     * Get the content of a remote file over SFTP
273
     *
274
     * @param string $filename
275
     *
276
     * @return string File content
277
     */
278
    public function grabRemoteFile($filename)
279
    {
280
        try {
281
            $sftp = ssh2_sftp($this->connection);
282
            return file_get_contents("ssh2.sftp://{$sftp}/{$filename}");
283
        } catch (Exception $e) {
284
            throw new ModuleException(__CLASS__, $e->getMessage());
285
        }
286
    }
287
288
    /**
289
     * Verify that a remote folder exists in the current remote path
290
     *
291
     * @param string $dirname
292
     *
293
     * @return void
294
     */
295 View Code Duplication
    public function seeRemoteDir($dirname)
296
    {
297
        try {
298
            $res = (bool) $this->grabRemoteDir($dirname);
299
        } catch (Exception $e) {
300
            $res = false;
301
        }
302
        \PHPUnit_Framework_Assert::assertTrue($res);
303
    }
304
305
    /**
306
     * Verify that a remote folder does not exist in the current remote path
307
     *
308
     * @param string $dirname
309
     *
310
     * @return void
311
     */
312 View Code Duplication
    public function dontSeeRemoteDir($dirname)
313
    {
314
        try {
315
            $res = (bool) $this->grabRemoteDir($dirname);
316
        } catch (Exception $e) {
317
            $res = false;
318
        }
319
        \PHPUnit_Framework_Assert::assertFalse($res);
320
    }
321
322
    /**
323
     * Get the remode folder content
324
     *
325
     * @param string $dirname
326
     *
327
     * @return string[] Array of file names and folder names
328
     */
329
    public function grabRemoteDir($dirname)
330
    {
331
        $res = null;
332
        try {
333
            $sftp = ssh2_sftp($this->connection);
334
            $res = scandir("ssh2.sftp://{$sftp}/{$dirname}");
335
        } catch (Exception $e) {
336
            throw new ModuleException(__CLASS__, $e->getMessage());
337
        }
338
        return $res;
339
    }
340
341
}
342