Completed
Push — master ( e2a184...b6fd99 )
by Joschi
04:50
created

PersistenceService::certifyVhost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
ccs 0
cts 6
cp 0
rs 9.4285
cc 1
eloc 5
nc 1
nop 2
crap 2
1
<?php
2
3
/**
4
 * admin
5
 *
6
 * @category    Tollwerk
7
 * @package     Tollwerk\Admin
8
 * @subpackage  Tollwerk\Admin\Infrastructure\Service
9
 * @author      Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright   Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Tollwerk\Admin\Infrastructure\Service;
38
39
use Tollwerk\Admin\Application\Contract\PersistenceAdapterFactoryInterface;
40
use Tollwerk\Admin\Application\Contract\PersistenceServiceInterface;
41
use Tollwerk\Admin\Application\Contract\ServiceServiceInterface;
42
use Tollwerk\Admin\Domain\Account\AccountInterface;
43
use Tollwerk\Admin\Domain\Vhost\VhostInterface;
44
use Tollwerk\Admin\Infrastructure\App;
45
use Tollwerk\Admin\Infrastructure\Persistence\AccountHelper;
46
use Tollwerk\Admin\Infrastructure\Shell\Directory;
47
48
/**
49
 * Persistence service
50
 *
51
 * @package Tollwerk\Admin
52
 * @subpackage Tollwerk\Admin\Infrastructure
53
 */
54
class PersistenceService implements PersistenceServiceInterface
55
{
56
    /**
57
     * Persistence adapter factory
58
     *
59
     * @var PersistenceAdapterFactoryInterface
60
     */
61
    protected $persistenceAdapterFactory;
62
    /**
63
     * Webservers scheduled for reload
64
     *
65
     * @var array
66
     */
67
    protected $reloadWebserver = [];
68
    /**
69
     * PHP-Restart flag
70
     *
71
     * @var array
72
     */
73
    protected $reloadPhp = [];
74
    /**
75
     * Service service
76
     *
77
     * @var ServiceServiceInterface
78
     */
79
    protected $serviceService;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param PersistenceAdapterFactoryInterface $persistenceAdapterFactory Persistence adapter factory
85
     * @param ServiceServiceInterface $serviceService Service service
86
     */
87
    public function __construct(
88
        PersistenceAdapterFactoryInterface $persistenceAdapterFactory,
89
        ServiceServiceInterface $serviceService
90
    ) {
91
        $this->persistenceAdapterFactory = $persistenceAdapterFactory;
92
        $this->serviceService = $serviceService;
93
    }
94
95
    /**
96
     * Create an account
97
     *
98
     * @param AccountInterface $account Account
99
     * @return void
100
     */
101
    public function createAccount(AccountInterface $account)
102
    {
103
    }
104
105
    /**
106
     * Delete an account
107
     *
108
     * @param AccountInterface $account Account
109
     * @return void
110
     */
111
    public function deleteAccount(AccountInterface $account)
112
    {
113
        // Delete all virtual hosts
114
        /** @var VhostInterface $vhost */
115
        foreach ($account->getVhosts() as $vhost) {
116
            $this->deleteVhost($account, $vhost);
117
        }
118
    }
119
120
    /**
121
     * Enable an account
122
     *
123
     * @param AccountInterface $account Account
124
     * @return void
125
     */
126
    public function enableAccount(AccountInterface $account)
127
    {
128
        // Enable all virtual hosts
129
        /** @var VhostInterface $vhost */
130
        foreach ($account->getVhosts() as $vhost) {
131
            $this->enableVhost($account, $vhost);
132
        }
133
    }
134
135
    /**
136
     * Disable an account
137
     *
138
     * @param AccountInterface $account Account
139
     * @return void
140
     */
141
    public function disableAccount(AccountInterface $account)
142
    {
143
        // Disable all virtual hosts
144
        /** @var VhostInterface $vhost */
145
        foreach ($account->getVhosts() as $vhost) {
146
            $this->disableVhost($account, $vhost);
147
        }
148
    }
149
150
    /**
151
     * Rename an account
152
     *
153
     * @param AccountInterface $account Account
154
     * @return void
155
     */
156
    public function renameAccount(AccountInterface $account)
157
    {
158
        // Rewrite all virtual hosts
159
        // Reload Apache
160
    }
161
162
    /**
163
     * Create a virtual host
164
     *
165
     * @param AccountInterface $account Account
166
     * @param VhostInterface $vhost Virtual host
167
     * @return void
168
     */
169
    public function createVhost(AccountInterface $account, VhostInterface $vhost)
170
    {
171
        $accountHelper = new AccountHelper($account);
172
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
173
        if (is_dir($availableVhost)) {
174
            throw new \RuntimeException(
175
                sprintf('Virtual host "%s" already exists', strval($vhost->getPrimaryDomain())),
176
                1476018313
177
            );
178
        }
179
180
        // Prepare the virtual host directory
181
        $this->prepareDirectory($availableVhost, $account->getName(), App::getConfig('general.group'));
182
183
        // Persist the virtual host
184
        $this->persistVhost($account, $vhost);
185
    }
186
187
    /**
188
     * Delete a virtual host
189
     *
190
     * @param AccountInterface $account Account
191
     * @param VhostInterface $vhost Virtual host
192
     * @return void
193
     */
194
    public function deleteVhost(AccountInterface $account, VhostInterface $vhost)
195
    {
196
        // Disable the virtual host first
197
        $this->disableVhost($account, $vhost);
198
199
        $accountHelper = new AccountHelper($account);
200
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
201
        if (is_dir($availableVhost)) {
202
            $this->deleteDirectory($availableVhost, $account->getName(), App::getConfig('general.group'));
203
        }
204
    }
205
206
    /**
207
     * Enable a virtual host
208
     *
209
     * @param AccountInterface $account Account
210
     * @param VhostInterface $vhost Virtual host
211
     * @return void
212
     * @throws \RuntimeException If the virtual host directory doesn't exist yet
213
     * @throws \RuntimeException If the virtual host is already enabled but cannot be renewed
214
     */
215
    public function enableVhost(AccountInterface $account, VhostInterface $vhost)
216
    {
217
        // If both the account and the virtual host are active: Enable the virtual host
218
        if ($account->isActive() && $vhost->isActive()) {
219
            $accountHelper = new AccountHelper($account);
220
            $availableVhost = $accountHelper->vhostDirectory($vhost, false);
221
222
            // If the virtual host directory doesn't exist yet: Error
223
            if (!is_dir($availableVhost)) {
224
                throw new \RuntimeException(
225
                    sprintf('Virtual host "%s" isn\'t persisted yet', strval($vhost->getPrimaryDomain())),
226
                    1476015113
227
                );
228
            }
229
230
            $enabledVhost = $accountHelper->vhostDirectory($vhost, true);
231
            $this->prepareDirectory(dirname($enabledVhost), $account->getName(), App::getConfig('general.group'));
232
233
            // If the virtual host is already enabled but cannot be renewed
234
            if (file_exists($enabledVhost) && !unlink($enabledVhost)) {
235
                throw new \RuntimeException(
236
                    sprintf(
237
                        'Virtual host "%s" is already enabled but cannot be renewed',
238
                        strval($vhost->getPrimaryDomain())
239
                    ),
240
                    1476016371
241
                );
242
            }
243
244
            $relativeEnabledVhost = '..'.substr($availableVhost, strlen($accountHelper->directory('config')));
245
            symlink($relativeEnabledVhost, $enabledVhost);
246
247
            // Reload apache
248
            $this->serviceService->reloadWebserver($vhost->getType());
249
        }
250
    }
251
252
    /**
253
     * Disable a virtual host
254
     *
255
     * @param AccountInterface $account Account
256
     * @param VhostInterface $vhost Virtual host
257
     * @return void
258
     */
259
    public function disableVhost(AccountInterface $account, VhostInterface $vhost)
260
    {
261
        $accountHelper = new AccountHelper($account);
262
        $enabledVhost = $accountHelper->vhostDirectory($vhost, true);
263
264
        // If the virtual host cannot be disabled
265
        if (file_exists($enabledVhost) && !unlink($enabledVhost)) {
266
            throw new \RuntimeException(
267
                sprintf('Virtual host "%s" cannot be disabled', strval($vhost->getPrimaryDomain())),
268
                1476016872
269
            );
270
        }
271
272
        // Reload apache
273
        $this->serviceService->reloadWebserver($vhost->getType());
274
    }
275
276
    /**
277
     * Redirect a virtual host
278
     *
279
     * @param AccountInterface $account Account
280
     * @param VhostInterface $vhost Virtual host
281
     * @return void
282
     */
283
    public function redirectVhost(AccountInterface $account, VhostInterface $vhost)
284
    {
285
        // Re-persist the virtual host
286
        $this->persistVhost($account, $vhost);
287
288
        // Reload apache
289
        $this->serviceService->reloadWebserver($vhost->getType());
290
    }
291
292
    /**
293
     * Configure the PHP version of a virtual host
294
     *
295
     * @param AccountInterface $account Account
296
     * @param VhostInterface $vhost Virtual host
297
     * @param string|null $oldPhpVersion Old PHP version
298
     * @return void
299
     * @throws \RuntimeException If the previous PHP configuration cannot be removed
300
     */
301
    public function phpVhost(AccountInterface $account, VhostInterface $vhost, $oldPhpVersion = null)
302
    {
303
        $accountHelper = new AccountHelper($account);
304
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
305
306
        // Find all existing PHP configurations
307
        foreach (glob($availableVhost.DIRECTORY_SEPARATOR.'{fpm-*.conf,*_fpm.include}', GLOB_BRACE) as $fpmConfig) {
308
            // If the previous PHP configuration cannot be removed
309
            if (!unlink($fpmConfig)) {
310
                throw new \RuntimeException(
311
                    sprintf('Cannot remove PHP configuration "%s"', basename($fpmConfig)),
312
                    1476019274
313
                );
314
            }
315
        }
316
317
        // Re-persist the virtual host
318
        $this->persistVhost($account, $vhost);
319
320
        // Reload PHP
321
        $newPhpVersion = $vhost->getPhp();
322
        if ($oldPhpVersion !== $newPhpVersion) {
323
            // If the old PHP version needs to be disabled
324
            if ($oldPhpVersion !== null) {
325
                $this->serviceService->reloadPhp($oldPhpVersion);
326
            }
327
328
            // If the new PHP version needs to be enabled
329
            if ($newPhpVersion !== null) {
330
                $this->serviceService->reloadPhp($newPhpVersion);
331
            }
332
        }
333
334
        // Reload apache
335
        $this->serviceService->reloadWebserver($vhost->getType());
336
    }
337
338
    /**
339
     * Configure a protocol based port for a virtual host
340
     *
341
     * @param AccountInterface $account Account
342
     * @param VhostInterface $vhost Virtual host
343
     * @return void
344
     */
345
    public function portVhost(AccountInterface $account, VhostInterface $vhost)
346
    {
347
        $accountHelper = new AccountHelper($account);
348
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
349
350
        // Find all existing PHP configurations
351
        foreach (glob($availableVhost.DIRECTORY_SEPARATOR.'{certbot.ini,*_ssl.include}', GLOB_BRACE) as $sslConfig) {
352
            // If the previous SSL configuration cannot be removed
353
            if (!unlink($sslConfig)) {
354
                throw new \RuntimeException(
355
                    sprintf('Cannot remove SSL configuration "%s"', basename($sslConfig)),
356
                    1476545093
357
                );
358
            }
359
        }
360
361
        // Re-persist the virtual host
362
        $this->persistVhost($account, $vhost);
363
364
        // Reload apache
365
        $this->serviceService->reloadWebserver($vhost->getType());
366
    }
367
368
    /**
369
     * Add a secondary domain to a virtual host
370
     *
371
     * @param AccountInterface $account Account
372
     * @param VhostInterface $vhost Virtual host
373
     * @return void
374
     */
375
    public function domainVhost(AccountInterface $account, VhostInterface $vhost)
376
    {
377
        // Re-persist the virtual host
378
        $this->persistVhost($account, $vhost);
379
380
        // Reload apache
381
        $this->serviceService->reloadWebserver($vhost->getType());
382
    }
383
384
    /**
385
     * Certifiy a virtual host
386
     *
387
     * @param AccountInterface $account Account
388
     * @param VhostInterface $vhost Virtual host
389
     * @return void
390
     */
391
    public function certifyVhost(AccountInterface $account, VhostInterface $vhost) {
392
        // Issue an SSL certificate
393
        $accountHelper = new AccountHelper($account);
394
        $certificateConfig = $accountHelper->vhostDirectory($vhost).DIRECTORY_SEPARATOR.'certbot.ini';
395
396
        $this->serviceService->certify($certificateConfig);
397
398
        // Reload apache
399
        $this->serviceService->reloadWebserver($vhost->getType());
400
    }
401
402
    /**
403
     * Prepare a directory
404
     *
405
     * @param string $directory Directory
406
     * @param string $user Owner user
407
     * @param string $group Owner group
408
     * @return string Prepared directory
409
     */
410
    protected function prepareDirectory($directory, $user, $group)
411
    {
412
        if (!is_dir($directory) && !Directory::create($directory, $user, $group)) {
413
            throw new \RuntimeException(sprintf('Could not prepare directory "%s"', $directory), 1476014854);
414
        }
415
        return $directory;
416
    }
417
418
    /**
419
     * Recursively delete a directory
420
     *
421
     * @param string $directory Directory
422
     * @param string $user Owner user
423
     * @param string $group Owner group
424
     * @throws \RuntimeException If a file or directory cannot be deleted
425
     */
426
    protected function deleteDirectory($directory, $user, $group)
427
    {
428
        $files = array_diff(scandir($directory), array('.', '..'));
429
        foreach ($files as $file) {
430
            // Recursive call if it's a directory
431
            if (is_dir($directory.DIRECTORY_SEPARATOR.$file)) {
432
                $this->deleteDirectory($directory.DIRECTORY_SEPARATOR.$file, $user, $group);
433
                continue;
434
            }
435
436
            // Delete the file
437
            if (!unlink($directory.DIRECTORY_SEPARATOR.$file)) {
438
                throw new \RuntimeException(
439
                    sprintf('Could not delete file "%s"', $directory.DIRECTORY_SEPARATOR.$file),
440
                    1476017676
441
                );
442
            }
443
        }
444
445
        // Delete the directory
446
        if (!Directory::delete($directory, $user, $group)) {
447
            throw new \RuntimeException(sprintf('Could not delete directory "%s"', $directory), 1476017747);
448
        }
449
    }
450
451
    /**
452
     * Persist a virtual host
453
     *
454
     * @param AccountInterface $account Account
455
     * @param VhostInterface $vhost Virtual host
456
     */
457
    protected function persistVhost(AccountInterface $account, VhostInterface $vhost)
458
    {
459
        // Persist all virtual host files
460
        $this->persistenceAdapterFactory
461
            ->makeVhostPersistenceAdapterStrategy($vhost->getType())
462
            ->persist($account, $vhost);
463
    }
464
}
465