Completed
Pull Request — master (#28)
by
unknown
07:51
created

SftpAdapter::getAuthentication()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.3332

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 4
cts 6
cp 0.6667
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 0
crap 3.3332
1
<?php
2
3
namespace League\Flysystem\Sftp;
4
5
use InvalidArgumentException;
6
use League\Flysystem\Adapter\AbstractFtpAdapter;
7
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
8
use League\Flysystem\AdapterInterface;
9
use League\Flysystem\Config;
10
use League\Flysystem\Util;
11
use LogicException;
12
use phpseclib\Net\SFTP;
13
use phpseclib\Crypt\RSA;
14
use phpseclib\System\SSH\Agent;
15
use RuntimeException;
16
17
class SftpAdapter extends AbstractFtpAdapter
18
{
19
    use StreamedCopyTrait;
20
21
    /**
22
     * @var int
23
     */
24
    protected $port = 22;
25
26
    /**
27
     * @var string
28
     */
29
    protected $privatekey;
30
31
    /**
32
     * @var bool
33
     */
34
    protected $useAgent = false;
35
36
    /**
37
     * @var Agent
38
     */
39
    private $agent;
40
41
    /**
42
     * @var array
43
     */
44
    protected $configurable = ['host', 'port', 'username', 'password', 'useAgent', 'agent', 'timeout', 'root', 'privateKey', 'permPrivate', 'permPublic', 'directoryPerm', 'NetSftpConnection'];
45
46
    /**
47
     * @var array
48
     */
49
    protected $statMap = ['mtime' => 'timestamp', 'size' => 'size'];
50
51
    /**
52
     * @var int
53
     */
54
    protected $directoryPerm = 0744;
55
56
    /**
57
     * Prefix a path.
58
     *
59
     * @param string $path
60
     *
61
     * @return string
62
     */
63 6
    protected function prefix($path)
64
    {
65 6
        return $this->root.ltrim($path, $this->separator);
66
    }
67
68
    /**
69
     * Set the private key (string or path to local file).
70
     *
71
     * @param string $key
72
     *
73
     * @return $this
74
     */
75
    public function setPrivateKey($key)
76
    {
77
        $this->privatekey = $key;
78
79
        return $this;
80
    }
81
82
    /**
83
     * @param boolean $useAgent
84
     *
85
     * @return $this
86
     */
87
    public function setUseAgent($useAgent)
88
    {
89
        $this->useAgent = (bool) $useAgent;
90
91
        return $this;
92
    }
93
94
    /**
95
     * @param Agent $agent
96
     *
97
     * @return $this
98
     */
99
    public function setAgent(Agent $agent)
100
    {
101
        $this->agent = $agent;
102
103
        return $this;
104
    }
105
106
    /**
107
     * Set permissions for new directory
108
     *
109
     * @param int $directoryPerm
110
     *
111
     * @return $this
112
     */
113 6
    public function setDirectoryPerm($directoryPerm)
114
    {
115 6
        $this->directoryPerm = $directoryPerm;
116
117 6
        return $this;
118
    }
119
120
    /**
121
     * Get permissions for new directory
122
     *
123
     * @return int
124
     */
125 3
    public function getDirectoryPerm()
126
    {
127 3
        return $this->directoryPerm;
128
    }
129
130
    /**
131
     * Inject the SFTP instance.
132
     *
133
     * @param SFTP $connection
134
     *
135
     * @return $this
136
     */
137 21
    public function setNetSftpConnection(SFTP $connection)
138
    {
139 21
        $this->connection = $connection;
140
141 21
        return $this;
142
    }
143
144
    /**
145
     * Connect.
146
     */
147 12
    public function connect()
148
    {
149 12
        $this->connection = $this->connection ?: new SFTP($this->host, $this->port, $this->timeout);
150 12
        $this->login();
151 9
        $this->setConnectionRoot();
152 6
    }
153
154
    /**
155
     * Login.
156
     *
157
     * @throws LogicException
158
     */
159 12
    protected function login()
160 2
    {
161 12
        $authentication = $this->getAuthentication();
162
163
        /*
164
         * We no longer need the password or private key. Remove them so that they cannot accidentally be exposed after
165
         * login (e.g. when a stacktrace or scope is dumped).
166
         */
167 12
        $this->password   = null;
168 12
        $this->privatekey = null;
169
170 12
        if (! $this->connection->login($this->username, $authentication)) {
171 3
            throw new LogicException('Could not login with username: '.$this->username.', host: '.$this->host);
172
        }
173
174 9
        if ($authentication instanceof Agent) {
0 ignored issues
show
Bug introduced by
The class phpseclib\System\SSH\Agent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
175
            $authentication->startSSHForwarding($this->connection);
176
        }
177 9
    }
178
179
    /**
180
     * Set the connection root.
181
     */
182 9
    protected function setConnectionRoot()
183
    {
184 9
        $root = $this->getRoot();
185
186 9
        if (! $root) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $root of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
187 3
            return;
188
        }
189
190 6
        if (! $this->connection->chdir($root)) {
191 3
            throw new RuntimeException('Root is invalid or does not exist: '.$root);
192
        }
193 3
        $this->root = $this->connection->pwd() . $this->separator;
194 3
    }
195
196
    /**
197
     * Get the password, either the private key or a plain text password.
198
     *
199
     * @return Agent|RSA|string
200
     */
201 12
    protected function getAuthentication()
202
    {
203 12
        if ($this->useAgent) {
204
            return $this->getAgent();
205
        }
206
207 12
        if ($this->privatekey) {
208
            return $this->getPrivateKey();
209
        }
210
211 12
        return $this->getPassword();
0 ignored issues
show
Deprecated Code introduced by
The method League\Flysystem\Sftp\SftpAdapter::getPassword() has been deprecated.

This method has been deprecated.

Loading history...
212
    }
213
214
    /**
215
     * Get the password, a plain text password.
216
     *
217
     * After login, this method may return null.
218
     *
219
     * @deprecated
220
     * @return string|null
221
     */
222 12
    public function getPassword()
223
    {
224 12
        return $this->password;
225
    }
226
227
    /**
228
     * Get the private get with the password or private key contents.
229
     *
230
     * @return RSA
231
     */
232
    protected function getPrivateKey()
233
    {
234
        if (@is_file($this->privatekey)) {
235
            $this->privatekey = file_get_contents($this->privatekey);
236
        }
237
238
        $key = new RSA();
239
240
        if ($this->password) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->password of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
241
            $key->setPassword($this->password);
242
        }
243
244
        $key->loadKey($this->privatekey);
245
246
        return $key;
247
    }
248
249
    /**
250
     * @return Agent|bool
251
     */
252
    public function getAgent()
253
    {
254
        if ( ! $this->agent instanceof Agent) {
0 ignored issues
show
Bug introduced by
The class phpseclib\System\SSH\Agent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
255
            $this->agent = new Agent();
256
        }
257
258
        return $this->agent;
259
    }
260
261
    /**
262
     * List the contents of a directory.
263
     *
264
     * @param string $directory
265
     * @param bool   $recursive
266
     *
267
     * @return array
268
     */
269 6
    protected function listDirectoryContents($directory, $recursive = true)
270
    {
271 6
        $result = [];
272 6
        $connection = $this->getConnection();
273 6
        $location = $this->prefix($directory);
274 6
        $listing = $connection->rawlist($location);
275
276 6
        if ($listing === false) {
277 3
            return [];
278
        }
279
280 6
        foreach ($listing as $filename => $object) {
281 6
            if (in_array($filename, ['.', '..'])) {
282 3
                continue;
283
            }
284
285 6
            $path = empty($directory) ? $filename : ($directory.'/'.$filename);
286 6
            $result[] = $this->normalizeListingObject($path, $object);
287
288 6
            if ($recursive && $object['type'] === NET_SFTP_TYPE_DIRECTORY) {
289 4
                $result = array_merge($result, $this->listDirectoryContents($path));
290 2
            }
291 4
        }
292
293 6
        return $result;
294
    }
295
296
    /**
297
     * Normalize a listing response.
298
     *
299
     * @param string $path
300
     * @param array  $object
301
     *
302
     * @return array
303
     */
304 6
    protected function normalizeListingObject($path, array $object)
305
    {
306 6
        $permissions = $this->normalizePermissions($object['permissions']);
307 6
        $type = ($object['type'] === 1) ? 'file' : 'dir' ;
308 6
        $timestamp = $object['mtime'];
309
310 6
        if ($type === 'dir') {
311 6
            return compact('path', 'timestamp', 'type');
312
        }
313
314 6
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
315 6
        $size = (int) $object['size'];
316
317 6
        return compact('path', 'timestamp', 'type', 'visibility', 'size');
318
    }
319
320
    /**
321
     * Disconnect.
322
     */
323 6
    public function disconnect()
324
    {
325 6
        $this->connection = null;
326 6
    }
327
328
    /**
329
     * @inheritdoc
330
     */
331 6
    public function write($path, $contents, Config $config)
332
    {
333 6
        if ($this->upload($path, $contents, $config) === false) {
334 6
            return false;
335
        }
336
337 6
        return compact('contents', 'visibility', 'path');
338
    }
339
340
    /**
341
     * @inheritdoc
342
     */
343 6
    public function writeStream($path, $resource, Config $config)
344
    {
345 6
        if ($this->upload($path, $resource, $config) === false) {
346 6
            return false;
347
        }
348
349 6
        return compact('visibility', 'path');
350
    }
351
352
    /**
353
     * Upload a file.
354
     *
355
     * @param string          $path
356
     * @param string|resource $contents
357
     * @param Config          $config
358
     * @return bool
359
     */
360 12
    public function upload($path, $contents, Config $config)
361
    {
362 12
        $connection = $this->getConnection();
363 12
        $this->ensureDirectory(Util::dirname($path));
364 12
        $config = Util::ensureConfig($config);
365
366 12
        if (! $connection->put($path, $contents, SFTP::SOURCE_STRING)) {
367 12
            return false;
368
        }
369
370 12
        if ($config && $visibility = $config->get('visibility')) {
371 6
            $this->setVisibility($path, $visibility);
372 4
        }
373
374 12
        return true;
375
    }
376
377
    /**
378
     * @inheritdoc
379
     */
380 6
    public function read($path)
381
    {
382 6
        $connection = $this->getConnection();
383
384 6
        if (($contents = $connection->get($path)) === false) {
385 6
            return false;
386
        }
387
388 6
        return compact('contents');
389
    }
390
391
    /**
392
     * @inheritdoc
393
     */
394 3
    public function readStream($path)
395
    {
396 3
        $stream = tmpfile();
397 3
        $connection = $this->getConnection();
398
399 3
        if ($connection->get($path, $stream) === false) {
400 3
            fclose($stream);
401 3
            return false;
402
        }
403
404 3
        rewind($stream);
405
406 3
        return compact('stream');
407
    }
408
409
    /**
410
     * @inheritdoc
411
     */
412 3
    public function update($path, $contents, Config $config)
413
    {
414 3
        return $this->write($path, $contents, $config);
415
    }
416
417
    /**
418
     * @inheritdoc
419
     */
420 3
    public function updateStream($path, $contents, Config $config)
421
    {
422 3
        return $this->writeStream($path, $contents, $config);
423
    }
424
425
    /**
426
     * @inheritdoc
427
     */
428 3
    public function delete($path)
429
    {
430 3
        $connection = $this->getConnection();
431
432 3
        return $connection->delete($path);
433
    }
434
435
    /**
436
     * @inheritdoc
437
     */
438 3
    public function rename($path, $newpath)
439
    {
440 3
        $connection = $this->getConnection();
441
442 3
        return $connection->rename($path, $newpath);
443
    }
444
445
    /**
446
     * @inheritdoc
447
     */
448 3
    public function deleteDir($dirname)
449
    {
450 3
        $connection = $this->getConnection();
451
452 3
        return $connection->delete($dirname, true);
453
    }
454
455
    /**
456
     * @inheritdoc
457
     */
458 39
    public function has($path)
459
    {
460 39
        return $this->getMetadata($path);
461
    }
462
463
    /**
464
     * @inheritdoc
465
     */
466 48
    public function getMetadata($path)
467
    {
468 48
        $connection = $this->getConnection();
469 48
        $info = $connection->stat($path);
470
471 48
        if ($info === false) {
472 12
            return false;
473
        }
474
475 39
        $result = Util::map($info, $this->statMap);
476 39
        $result['type'] = $info['type'] === NET_SFTP_TYPE_DIRECTORY ? 'dir' : 'file';
477 39
        $result['visibility'] = $info['permissions'] & $this->permPublic ? 'public' : 'private';
478
479 39
        return $result;
480
    }
481
482
    /**
483
     * @inheritdoc
484
     */
485 6
    public function getTimestamp($path)
486
    {
487 6
        return $this->getMetadata($path);
488
    }
489
490
    /**
491
     * @inheritdoc
492
     */
493 3
    public function getMimetype($path)
494
    {
495 3
        if (! $data = $this->read($path)) {
496 3
            return false;
497
        }
498
499 3
        $data['mimetype'] = Util::guessMimeType($path, $data['contents']);
500
501 3
        return $data;
502
    }
503
504
    /**
505
     * @inheritdoc
506
     */
507 3
    public function createDir($dirname, Config $config)
508
    {
509 3
        $connection = $this->getConnection();
510
511 3
        if (! $connection->mkdir($dirname, $this->directoryPerm, true)) {
512 3
            return false;
513
        }
514
515 3
        return ['path' => $dirname];
516
    }
517
518
    /**
519
     * @inheritdoc
520
     */
521 6
    public function getVisibility($path)
522
    {
523 6
        return $this->getMetadata($path);
524
    }
525
526
    /**
527
     * @inheritdoc
528
     */
529 12
    public function setVisibility($path, $visibility)
530
    {
531 12
        $visibility = ucfirst($visibility);
532
533 12
        if (! isset($this->{'perm'.$visibility})) {
534 3
            throw new InvalidArgumentException('Unknown visibility: '.$visibility);
535
        }
536
537 9
        $connection = $this->getConnection();
538
539 9
        return $connection->chmod($this->{'perm'.$visibility}, $path);
540
    }
541
542
    /**
543
     * @inheritdoc
544
     */
545 6
    public function isConnected()
546
    {
547 6
        if ($this->connection instanceof SFTP && $this->connection->isConnected()) {
0 ignored issues
show
Bug introduced by
The class phpseclib\Net\SFTP does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
548 3
            return true;
549
        }
550
551 3
        return false;
552
    }
553
}
554