Completed
Push — master ( a736b0...567a90 )
by Sebastian
04:59
created

Sftp::validateRemotePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
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
     * @var int
39
     */
40
    protected $time;
41
42
    /**
43
     * @var string
44
     */
45
    protected $privateKey;
46
47
    /**
48
     * (non-PHPDoc)
49
     *
50
     * @see    \phpbu\App\Backup\Sync::setup()
51
     * @param  array $config
52
     * @throws \phpbu\App\Backup\Sync\Exception
53
     * @throws \phpbu\App\Exception
54
     */
55 12
    public function setup(array $config)
56
    {
57 12
        if (!Util\Arr::isSetAndNotEmptyString($config, 'password') && !Util\Arr::isSetAndNotEmptyString($config, 'private_key')) {
58 4
            throw new Exception('\'password\' or \'private_key\' must be presented');
59
        }
60 8
        parent::setup($config);
61
62 8
        $this->time = time();
63 8
        $privateKey = Util\Arr::getValue($config, 'key', '');
64 8
        if (!empty($privateKey)) {
65
            // get absolute private key path
66 2
            $privateKey = Util\Path::toAbsolutePath($privateKey, Configuration::getWorkingDirectory());
67 2
            if (!file_exists($privateKey)) {
68 1
                throw new Exception("Private key not found at specified path");
69
            }
70
        }
71 7
        $this->privateKey = $privateKey;
72 7
        $this->remotePath = new Path($config['path'], $this->time);
73
74 7
        $this->setUpCleanable($config);
75 7
    }
76
77
    /**
78
     * Check for required loaded libraries or extensions.
79
     *
80
     * @throws \phpbu\App\Backup\Sync\Exception
81
     */
82 8
    protected function checkRequirements()
83
    {
84 8
        if (!class_exists('\\phpseclib\\Net\\SFTP')) {
85
            throw new Exception('phpseclib not installed - use composer to install "phpseclib/phpseclib" version 2.x');
86
        }
87 8
    }
88
89
    /**
90
     * Return implemented (*)TP protocol name.
91
     *
92
     * @return string
93
     */
94 1
    protected function getProtocolName()
95
    {
96 1
        return 'SFTP';
97
    }
98
99
    /**
100
     * (non-PHPDoc)
101
     *
102
     * @see    \phpbu\App\Backup\Sync::sync()
103
     * @param  \phpbu\App\Backup\Target $target
104
     * @param  \phpbu\App\Result        $result
105
     * @throws \phpbu\App\Backup\Sync\Exception
106
     */
107 4
    public function sync(Target $target, Result $result)
108
    {
109 4
        $sftp           = $this->createClient();
110 4
        $remoteFilename = $target->getFilename();
111 4
        $localFile      = $target->getPathname();
112
113 4
        $this->validateRemotePath();
114
115 4
        foreach ($this->getRemoteDirectoryList() as $dir) {
116 4
            if (!$sftp->is_dir($dir)) {
117 2
                $result->debug(sprintf('creating remote dir \'%s\'', $dir));
118 2
                $sftp->mkdir($dir);
119
            }
120 4
            $result->debug(sprintf('change to remote dir \'%s\'', $dir));
121 4
            $sftp->chdir($dir);
122
        }
123
124 4
        $result->debug(sprintf('store file \'%s\' as \'%s\'', $localFile, $remoteFilename));
125 4
        $result->debug(sprintf('last error \'%s\'', $sftp->getLastSFTPError()));
126
127 4
        if (!$sftp->put($remoteFilename, $localFile, phpseclib\Net\SFTP::SOURCE_LOCAL_FILE)) {
128 1
            throw new Exception(sprintf('error uploading file: %s - %s', $localFile, $sftp->getLastSFTPError()));
129
        }
130
131
        // run remote cleanup
132 3
        $this->cleanup($target, $result);
133 3
    }
134
135
    /**
136
     * Create a sftp handle.
137
     *
138
     * @return \phpseclib\Net\SFTP
139
     * @throws \phpbu\App\Backup\Sync\Exception
140
     */
141
    protected function createClient() : phpseclib\Net\SFTP
142
    {
143
        if (!$this->sftp) {
144
            // silence phpseclib errors
145
            $old  = error_reporting(0);
146
            $this->sftp = new phpseclib\Net\SFTP($this->host);
147
            $auth = $this->getAuth();
148
149
            if (!$this->sftp->login($this->user, $auth)) {
150
                error_reporting($old);
151
                throw new Exception(
152
                    sprintf(
153
                        'authentication failed for %s@%s%s',
154
                        $this->user,
155
                        $this->host,
156
                        empty($this->password) ? '' : ' with password ****'
157
                    )
158
                );
159
            }
160
            // restore old error reporting
161
            error_reporting($old);
162
        }
163
164
        return $this->sftp;
165
    }
166
167
    /**
168
     * If a relative path is configured, determine absolute path and update local remote.
169
     *
170
     * @throws \phpbu\App\Backup\Sync\Exception
171
     */
172 4
    protected function validateRemotePath()
173
    {
174 4
        if (!Util\Path::isAbsolutePath($this->remotePath->getPath())) {
175 1
            $sftp             = $this->createClient();
176 1
            $this->remotePath = new Path($sftp->realpath('.') . '/' . $this->remotePath->getPathRaw(), $this->time);
177
        }
178 4
    }
179
180
    /**
181
     * Return a phpseclib authentication thingy.
182
     *
183
     * @return \phpseclib\Crypt\RSA|string
184
     */
185
    private function getAuth()
186
    {
187
        $auth = $this->password;
188
        // if private key should be used
189
        if ($this->privateKey) {
190
            $auth = new phpseclib\Crypt\RSA();
191
            $auth->loadKey(file_get_contents($this->privateKey));
192
            if ($this->password) {
193
                $auth->setPassword($this->password);
194
            }
195
        }
196
        return $auth;
197
    }
198
199
    /**
200
     * Return list of remote directories to travers.
201
     *
202
     * @return array
203
     */
204 4
    private function getRemoteDirectoryList() : array
205
    {
206 4
        return Util\Path::getDirectoryListFromAbsolutePath($this->remotePath->getPath());
207
    }
208
209
    /**
210
     * Creates collector for SFTP
211
     *
212
     * @param  \phpbu\App\Backup\Target $target
213
     * @return \phpbu\App\Backup\Collector
214
     * @throws \phpbu\App\Backup\Sync\Exception
215
     */
216 1
    protected function createCollector(Target $target): Collector
217
    {
218 1
        return new Collector\Sftp($target, $this->remotePath, $this->createClient());
219
    }
220
}
221