1
|
|
|
<?php |
2
|
|
|
namespace Codeception\Extension; |
3
|
|
|
|
4
|
|
|
use Codeception\Exception\ModuleException; |
5
|
|
|
use Codeception\Exception\ModuleConfigException; |
6
|
|
|
use Codeception\Module; |
7
|
|
|
use \SplFileObject; |
8
|
|
|
use \RuntimeException; |
9
|
|
|
use \Exception; |
10
|
|
|
|
11
|
|
|
class SecureShell extends Module |
12
|
|
|
{ |
13
|
|
|
|
14
|
|
|
const DEFAULT_PORT = 22; |
15
|
|
|
const AUTH_PASSWORD = 1; |
16
|
|
|
const AUTH_PUBKEY = 2; |
17
|
|
|
const AUTH_HOSTKEY = 3; |
18
|
|
|
const AUTH_AGENT = 4; |
19
|
|
|
const AUTH_NONE = 0; |
20
|
|
|
|
21
|
|
|
protected $config = []; |
22
|
|
|
|
23
|
|
|
protected $requiredFields = []; |
24
|
|
|
|
25
|
|
|
protected static $knownHostsFile = '~/.ssh/known_hosts'; // configuration |
26
|
|
|
|
27
|
|
|
protected static $acceptUnknownHost = true; // configuration |
28
|
|
|
|
29
|
|
|
protected $tunnels = []; |
30
|
|
|
|
31
|
|
|
protected $connection; |
32
|
|
|
|
33
|
|
|
private $output; |
34
|
|
|
|
35
|
|
|
public function openConnection($host, |
36
|
|
|
$port = SecureShell::DEFAULT_PORT, |
37
|
|
|
$auth = SecureShell::AUTH_PASSWORD, |
38
|
|
|
...$args) |
39
|
|
|
{ |
40
|
|
|
$uid = null; |
41
|
|
|
$callbacks = array('disconnect' => [$this, '_disconnect']); |
42
|
|
|
|
43
|
|
|
try { |
44
|
|
|
$connection = ssh2_connect($host, $port, $callbacks); |
45
|
|
|
if (!$connection) { |
46
|
|
|
throw new ModuleException(get_class($this), "Unable to connect to {$host} on port {$port}"); |
47
|
|
|
} else { |
48
|
|
|
$fp = $this->__checkFingerprint($connection); |
49
|
|
|
|
50
|
|
|
if ($this->__authenticate($connection, $auth, ...$args) === false) { |
51
|
|
|
throw new ModuleException(get_class($this), "Authentication failed on server {$host}:{$port}"); |
52
|
|
|
} else { |
53
|
|
|
$uid = hash('crc32', uniqid($fp), false); |
54
|
|
|
$this->connection = $connection; |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
} catch (ModuleException $e) { |
58
|
|
|
throw $e; |
59
|
|
|
} catch (Exception $e) { |
60
|
|
|
throw new ModuleException(get_class($this), $e->getMessage()); |
61
|
|
|
} |
62
|
|
|
return $uid; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
public function closeConnection() { |
66
|
|
|
$this->connection = null; |
67
|
|
|
return true; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
public function getConnection() { |
71
|
|
|
return $this->connection; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
protected function __authenticate($connection, $method, ...$args) |
75
|
|
|
{ |
76
|
|
|
switch ($method) { |
77
|
|
|
case SecureShell::AUTH_PASSWORD: |
78
|
|
|
return ssh2_auth_password($connection, ...$args); |
79
|
|
|
case SecureShell::AUTH_PUBKEY: |
80
|
|
|
return ssh2_auth_pubkey_file($connection, ...$args); |
81
|
|
|
case SecureShell::AUTH_HOSTKEY: |
82
|
|
|
return ssh2_auth_hostbased_file($connection, ...$args); |
83
|
|
|
case SecureShell::AUTH_AGENT: |
84
|
|
|
return ssh2_auth_agent($connection, ...$args); |
85
|
|
|
case SecureShell::AUTH_NONE: |
86
|
|
|
return ssh2_auth_none($connection, ...$args); |
87
|
|
|
default: |
88
|
|
|
throw new ModuleException(get_class($this), 'Unsupported authentication method'); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
protected function __checkFingerprint($connection) |
93
|
|
|
{ |
94
|
|
|
$knownHost = false; |
95
|
|
|
try { |
96
|
|
|
$fingerprint = ssh2_fingerprint($connection, SSH2_FINGERPRINT_MD5 | SSH2_FINGERPRINT_HEX); |
97
|
|
|
$file = new SplFileObject(static::$knownHostsFile); |
98
|
|
|
$file->setFlags(SplFileObject::READ_CSV); |
99
|
|
|
$file->setCsvControl(' '); |
100
|
|
|
foreach ($file as $entry) { |
101
|
|
|
list(,, $fp) = $entry; |
102
|
|
|
$knownHost = (strcmp($fp, $fingerprint) !== 0); |
103
|
|
|
if ($knownHost === true) { |
104
|
|
|
break; |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
$knownHost = $knownHost || static::$acceptUnknownHost; |
108
|
|
|
|
109
|
|
|
if ($knownHost === false) { |
110
|
|
|
throw new ModuleException(get_class($this), 'Unable to verify server identity!'); |
111
|
|
|
} |
112
|
|
|
} catch (RuntimeException $e) { |
113
|
|
|
if (static::$acceptUnknownHost === false) { |
114
|
|
|
throw new ModuleException(get_class($this), 'Unable to verify server identity!'); |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
return $fingerprint; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
protected function __disconnect() |
121
|
|
|
{ |
122
|
|
|
$this->connection = null; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** Remote Commands methods **/ |
126
|
|
|
|
127
|
|
|
public function runRemoteCommand($command) |
128
|
|
|
{ |
129
|
|
|
try { |
130
|
|
|
$stream = ssh2_exec($this->connection, $command); |
131
|
|
|
stream_set_blocking($stream, true); |
132
|
|
|
$errStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); |
133
|
|
|
$this->output['STDOUT'] = stream_get_contents($stream); |
134
|
|
|
$this->output['STDERR'] = stream_get_contents($errStream); |
135
|
|
|
return $this->output; |
136
|
|
|
} catch (Exception $e) { |
137
|
|
|
throw new ModuleException(get_class($this), $e->getMessage()); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function seeInRemoteOutput($text) |
142
|
|
|
{ |
143
|
|
|
\PHPUnit_Framework_Assert::assertContains($text, $this->output['STDOUT']); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
public function dontSeeInRemoteOutput($text) |
147
|
|
|
{ |
148
|
|
|
\PHPUnit_Framework_Assert::assertNotContains($text, $this->output['STDOUT']); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** Remote Files methods **/ |
152
|
|
|
|
153
|
|
|
public function seeRemoteFile($filename) |
154
|
|
|
{ |
155
|
|
|
$sftp = ssh2_sftp($this->connection); |
156
|
|
|
$res = ssh2_sftp_stat($sftp, $filename); |
157
|
|
|
\PHPUnit_Framework_Assert::assertNotEmpty($res); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
public function dontSeeRemoteFile($filename) |
161
|
|
|
{ |
162
|
|
|
$sftp = ssh2_sftp($this->connection); |
163
|
|
|
try { |
164
|
|
|
$res = (bool) ssh2_sftp_stat($sftp, $filename); |
165
|
|
|
} catch (Exception $e) { |
166
|
|
|
$res = false; |
167
|
|
|
} |
168
|
|
|
\PHPUnit_Framework_Assert::assertFalse($res); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function grabRemoteFile($filename) |
172
|
|
|
{ |
173
|
|
|
try { |
174
|
|
|
$sftp = ssh2_sftp($this->connection); |
175
|
|
|
return file_get_contents("ssh2.sftp://{$sftp}/{$filename}"); |
176
|
|
|
} catch (Exception $e) { |
177
|
|
|
throw new ModuleException(get_class($this), $e->getMessage()); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** Remote Dir methods **/ |
182
|
|
|
|
183
|
|
View Code Duplication |
public function seeRemoteDir($dirname) |
|
|
|
|
184
|
|
|
{ |
185
|
|
|
try { |
186
|
|
|
$res = (bool) $this->grabRemoteDir($dirname); |
187
|
|
|
} catch (Exception $e) { |
188
|
|
|
$res = false; |
189
|
|
|
} |
190
|
|
|
\PHPUnit_Framework_Assert::assertTrue($res); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
View Code Duplication |
public function dontSeeRemoteDir() |
|
|
|
|
194
|
|
|
{ |
195
|
|
|
try { |
196
|
|
|
$res = (bool) $this->grabRemoteDir($dirname); |
|
|
|
|
197
|
|
|
} catch (Exception $e) { |
198
|
|
|
$res = false; |
199
|
|
|
} |
200
|
|
|
\PHPUnit_Framework_Assert::assertFalse($res); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
public function grabRemoteDir($dirname) |
204
|
|
|
{ |
205
|
|
|
$res = null; |
206
|
|
|
try { |
207
|
|
|
$sftp = ssh2_sftp($this->connection); |
208
|
|
|
$res = scandir("ssh2.sftp://{$sftp}/{$dirname}"); |
209
|
|
|
} catch (Exception $e) { |
210
|
|
|
throw new ModuleException(get_class($this), $e->getMessage()); |
211
|
|
|
} |
212
|
|
|
return $res; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** Tunnel methods **/ |
216
|
|
|
|
217
|
|
|
public function openRemoteTunnel() |
218
|
|
|
{ |
219
|
|
|
|
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function closeRemoteTunnel() |
223
|
|
|
{ |
224
|
|
|
|
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
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.