Sftp::createClient()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 14
nc 3
nop 0
dl 0
loc 24
ccs 0
cts 15
cp 0
crap 20
rs 9.7998
c 1
b 0
f 0
1
<?php
2
namespace phpbu\App\Backup\Sync;
3
4
use phpbu\App\Backup\Collector;
5
use phpbu\App\Backup\Path;
6
use phpbu\App\Backup\Target;
7
use phpbu\App\Configuration;
8
use phpbu\App\Result;
9
use phpbu\App\Util;
10
use phpseclib;
11
12
/**
13
 * Sftp sync
14
 *
15
 * @package    phpbu
16
 * @subpackage Backup
17
 * @author     Sebastian Feldmann <[email protected]>
18
 * @copyright  Sebastian Feldmann <[email protected]>
19
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
20
 * @link       http://phpbu.de/
21
 * @since      Class available since Release 1.0.0
22
 */
23
class Sftp extends Xtp
24
{
25
    /**
26
     * @var phpseclib\Net\SFTP
27
     */
28
    protected $sftp;
29
30
    /**
31
     * Remote path where to put the backup
32
     *
33
     * @var Path
34
     */
35
    protected $remotePath;
36
37
    /**
38
     * Remote port of sftp server
39
     *
40
     * @var string
41
     */
42
    protected $port;
43
44
    /**
45
     * @var int
46
     */
47
    protected $time;
48
49
    /**
50
     * @var string
51
     */
52
    protected $privateKey;
53
54
    /**
55
     * (non-PHPDoc)
56
     *
57
     * @see    \phpbu\App\Backup\Sync::setup()
58
     * @param  array $config
59
     * @throws \phpbu\App\Backup\Sync\Exception
60
     * @throws \phpbu\App\Exception
61
     */
62 11
    public function setup(array $config)
63
    {
64
        // make sure either password or private key is configured
65 11
        if (!Util\Arr::isSetAndNotEmptyString($config, 'password')
66 11
         && !Util\Arr::isSetAndNotEmptyString($config, 'private_key')) {
67 4
            throw new Exception('\'password\' or \'private_key\' must be presented');
68
        }
69 7
        parent::setup($config);
70
71 7
        $this->time = time();
72 7
        $privateKey = Util\Arr::getValue($config, 'key', '');
73 7
        if (!empty($privateKey)) {
74
            // get absolute private key path
75 1
            $privateKey = Util\Path::toAbsolutePath($privateKey, Configuration::getWorkingDirectory());
76 1
            if (!file_exists($privateKey)) {
77 1
                throw new Exception("Private key not found at specified path");
78
            }
79
        }
80 6
        $this->privateKey = $privateKey;
81 6
        $this->remotePath = new Path($config['path'], $this->time);
82 6
        $this->port       = Util\Arr::getValue($config, 'port', '22');
83
84 6
        $this->setUpCleanable($config);
85 6
    }
86
87
    /**
88
     * Check for required loaded libraries or extensions.
89
     *
90
     * @throws \phpbu\App\Backup\Sync\Exception
91
     */
92 7
    protected function checkRequirements()
93
    {
94 7
        if (!class_exists('\\phpseclib\\Net\\SFTP')) {
95
            throw new Exception('phpseclib not installed - use composer to install "phpseclib/phpseclib" version 2.x');
96
        }
97 7
    }
98
99
    /**
100
     * Return implemented (*)TP protocol name.
101
     *
102
     * @return string
103
     */
104 1
    protected function getProtocolName()
105
    {
106 1
        return 'SFTP';
107
    }
108
109
    /**
110
     * (non-PHPDoc)
111
     *
112
     * @see    \phpbu\App\Backup\Sync::sync()
113
     * @param  \phpbu\App\Backup\Target $target
114
     * @param  \phpbu\App\Result        $result
115
     * @throws \phpbu\App\Backup\Sync\Exception
116
     */
117 4
    public function sync(Target $target, Result $result)
118
    {
119 4
        $sftp           = $this->createClient();
120 4
        $remoteFilename = $target->getFilename();
121 4
        $localFile      = $target->getPathname();
122
123 4
        $this->validateRemotePath();
124
125 4
        foreach ($this->getRemoteDirectoryList() as $dir) {
126 4
            if (!$sftp->is_dir($dir)) {
127 2
                $result->debug(sprintf('creating remote dir \'%s\'', $dir));
128 2
                $sftp->mkdir($dir);
129
            }
130 4
            $result->debug(sprintf('change to remote dir \'%s\'', $dir));
131 4
            $sftp->chdir($dir);
132
        }
133
134 4
        $result->debug(sprintf('store file \'%s\' as \'%s\'', $localFile, $remoteFilename));
135 4
        $result->debug(sprintf('last error \'%s\'', $sftp->getLastSFTPError()));
136
137 4
        if (!$sftp->put($remoteFilename, $localFile, phpseclib\Net\SFTP::SOURCE_LOCAL_FILE)) {
138 1
            throw new Exception(sprintf('error uploading file: %s - %s', $localFile, $sftp->getLastSFTPError()));
139
        }
140
141
        // run remote cleanup
142 3
        $this->cleanup($target, $result);
143 3
    }
144
145
    /**
146
     * Create a sftp handle.
147
     *
148
     * @return \phpseclib\Net\SFTP
149
     * @throws \phpbu\App\Backup\Sync\Exception
150
     */
151
    protected function createClient() : phpseclib\Net\SFTP
152
    {
153
        if (!$this->sftp) {
154
            // silence phpseclib errors
155
            $old  = error_reporting(0);
156
            $this->sftp = new phpseclib\Net\SFTP($this->host, $this->port);
0 ignored issues
show
Bug introduced by
$this->port of type string is incompatible with the type integer expected by parameter $port of phpseclib\Net\SFTP::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
            $this->sftp = new phpseclib\Net\SFTP($this->host, /** @scrutinizer ignore-type */ $this->port);
Loading history...
157
            $auth = $this->getAuth();
158
159
            if (!$this->sftp->login($this->user, $auth)) {
160
                error_reporting($old);
161
                throw new Exception(
162
                    sprintf(
163
                        'authentication failed for %s@%s%s',
164
                        $this->user,
165
                        $this->host,
166
                        empty($this->password) ? '' : ' with password ****'
167
                    )
168
                );
169
            }
170
            // restore old error reporting
171
            error_reporting($old);
172
        }
173
174
        return $this->sftp;
175
    }
176
177
    /**
178
     * If a relative path is configured, determine absolute path and update local remote.
179
     *
180
     * @throws \phpbu\App\Backup\Sync\Exception
181
     */
182 4
    protected function validateRemotePath()
183
    {
184 4
        if (!Util\Path::isAbsolutePath($this->remotePath->getPath())) {
185 1
            $sftp             = $this->createClient();
186 1
            $this->remotePath = new Path($sftp->realpath('.') . '/' . $this->remotePath->getPathRaw(), $this->time);
187
        }
188 4
    }
189
190
    /**
191
     * Return a phpseclib authentication thingy.
192
     *
193
     * @return \phpseclib\Crypt\RSA|string
194
     */
195
    private function getAuth()
196
    {
197
        $auth = $this->password;
198
        // if private key should be used
199
        if ($this->privateKey) {
200
            $auth = new phpseclib\Crypt\RSA();
201
            $auth->loadKey(file_get_contents($this->privateKey));
202
            if ($this->password) {
203
                $auth->setPassword($this->password);
204
            }
205
        }
206
        return $auth;
207
    }
208
209
    /**
210
     * Return list of remote directories to travers.
211
     *
212
     * @return array
213
     */
214 4
    private function getRemoteDirectoryList() : array
215
    {
216 4
        return Util\Path::getDirectoryListFromAbsolutePath($this->remotePath->getPath());
217
    }
218
219
    /**
220
     * Creates collector for SFTP
221
     *
222
     * @param  \phpbu\App\Backup\Target $target
223
     * @return \phpbu\App\Backup\Collector
224
     * @throws \phpbu\App\Backup\Sync\Exception
225
     */
226 1
    protected function createCollector(Target $target): Collector
227
    {
228 1
        return new Collector\Sftp($target, $this->remotePath, $this->createClient());
229
    }
230
}
231