├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── test.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── BackupShieldServiceProvider.php ├── Encryption.php ├── Factories │ └── Password.php ├── Listeners │ └── PasswordProtectZip.php └── config │ └── backup-shield.php └── tests ├── BackupShieldTests.php └── resources └── test.zip /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Found a bug that you want to report? 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **System/environment:** 14 | - Laravel-version: 15 | - PHP-version: 16 | - Laravel Backup Shield-version: 17 | 18 | **backup-shield.php-config:** 19 | Please provide a copy of your backup-shield.php-config. ⚠️ Please obscure/remove your password if you store it as plaintext. 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | php-tests: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | include: 12 | - php: 8.0 13 | illuminate: ^8.0 14 | - php: 7.4 15 | illuminate: ^8.0 16 | - php: 7.3 17 | illuminate: ^8.0 18 | - php: 7.3 19 | illuminate: ^7.0 20 | - php: 7.3 21 | illuminate: ^6.0 22 | 23 | name: PHP ${{ matrix.php }} - Illuminate ${{ matrix.illuminate }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | 34 | - name: Update composer 35 | run: composer self-update --2 36 | 37 | - name: Install dependencies 38 | run: composer require "illuminate/support:${{ matrix.illuminate }}" --no-interaction --no-progress --no-suggest 39 | 40 | - name: Execute tests 41 | run: vendor/bin/phpunit 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .DS_Store 4 | .phpunit.result.cache 5 | *.old 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | build: 5 | environment: 6 | php: 7 | version: 7.4 8 | 9 | # see https://pecl.php.net/ 10 | pecl_extensions: 11 | - zip 12 | 13 | checks: 14 | php: 15 | remove_extra_empty_lines: true 16 | remove_php_closing_tag: true 17 | remove_trailing_whitespace: true 18 | fix_use_statements: 19 | remove_unused: true 20 | preserve_multiple: false 21 | preserve_blanklines: true 22 | order_alphabetically: true 23 | fix_php_opening_tag: true 24 | fix_linefeed: true 25 | fix_line_ending: true 26 | fix_identation_4spaces: true 27 | fix_doc_comments: true 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | dist: xenial 4 | 5 | os: linux 6 | 7 | before_script: 8 | - travis_retry composer self-update 9 | - travis_retry composer require "illuminate/support:${ILLUMINATE_VERSION}" 10 | 11 | script: 12 | - composer test 13 | 14 | jobs: 15 | include: 16 | - php: 7.3 17 | env: 18 | - ILLUMINATE_VERSION=^6.0 19 | - php: 7.3 20 | env: 21 | - ILLUMINATE_VERSION=^7.0 22 | - php: 7.4 23 | env: 24 | - ILLUMINATE_VERSION=^7.0 25 | - php: 7.3 26 | env: 27 | - ILLUMINATE_VERSION=^8.0 28 | - php: 7.4 29 | env: 30 | - ILLUMINATE_VERSION=^8.0 31 | - php: 8.0 32 | env: 33 | - ILLUMINATE_VERSION=^7.0 34 | - php: 8.0 35 | env: 36 | - ILLUMINATE_VERSION=^8.0 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Marcus Olsson 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Backup Shield 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Build Status][ico-build]][link-build] 6 | [![Scrutinizer Score][ico-scrutinizer]][link-scrutinizer] 7 | 8 | ![backup-shield](https://user-images.githubusercontent.com/907114/40585078-b42b31ba-61ac-11e8-9db6-b5497e156f5a.png) 9 | 10 | **⚠️ Password-protection/encryption can now be done natively in [spatie/laravel-backup](https://github.com/spatie/laravel-backup). If you use v6 or v7 of spatie/laravel-backup there is no longer a need to use this package. The development of this package has therefore been halted.** 11 | 12 | **Thanks for using olssonm/laravel-backup-shield!** 13 | 14 | ## Secure your backups 15 | 16 | **This package helps you encrypt and password-protect your backups taken with [Spatie's](https://github.com/spatie) fantastic [spatie/laravel-backup](https://github.com/spatie/laravel-backup)-package.** 17 | 18 | Backup Shield simply listens for when the .zip-file generated by Laravel-backup is done, grabs it and applies your password and encryption of your liking. 19 | 20 | *Using older versions of Laravel? Check out the [v1 branch](https://github.com/olssonm/laravel-backup-shield/tree/v1) (for Laravel 5.2) and the [v2 branch](https://github.com/olssonm/laravel-backup-shield/tree/v2).* 21 | 22 | ## Requirements 23 | 24 | `php: ^7.3|^8.0` 25 | `ext-zip: ^1.14` 26 | `laravel: ^6|^7|^8` 27 | 28 | An appropriate zip-extension should be come with your PHP-install since PHP 7.2. If you for some reason don't have it installed – and don't want to install/upgrade it – look a versions prior to v3.4 of this package. 29 | 30 | ## Installation 31 | 32 | ```bash 33 | composer require olssonm/laravel-backup-shield 34 | ``` 35 | 36 | ## Configuration 37 | 38 | Publish your configuration using `php artisan vendor:publish` and select `BackupShieldServiceProvider`. Or directly via ```php artisan vendor:publish --provider="Olssonm\BackupShield\BackupShieldServiceProvider"```. 39 | 40 | You only have the ability to set two different options; password and encryption. 41 | 42 | ```php 43 | // Default configuration; backup-shield.php 44 | return [ 45 | 'password' => env('APP_KEY'), 46 | 'encryption' => \Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT 47 | ]; 48 | ``` 49 | 50 | #### Password 51 | 52 | Your password (*duh*). The default is the application key (`APP_KEY` in your .env-file). You might want to set something more appropriate. Remember to use long strings and to keep your password safe – **without it you will never be able to open your backup**. 53 | 54 | Set to `NULL` if you want to keep your backup without a password. 55 | 56 | #### Encryption 57 | 58 | Set your type of encryption. Available options are: 59 | 60 | `\Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT` (AES 128) 61 | `\Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_128` (AES 128) 62 | `\Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_192` (AES 192) 63 | `\Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_256` (AES 256) 64 | 65 | #### Regarding the layered archive 66 | 67 | This package adds the backup-zip created by spatie/laravel-backup inside a new password protected archive. This is to disable its contents to be able to be viewed without a password – instead only backup.zip will be displayed. Becouse, even without a password, a zip's contents (i.e. the file- and folder names) can be extracted. 68 | 69 | ## Testing 70 | 71 | ``` bash 72 | $ composer test 73 | ``` 74 | 75 | or 76 | 77 | ``` bash 78 | $ phpunit 79 | ``` 80 | 81 | ## License 82 | 83 | The MIT License (MIT). Please see the [LICENSE.md](LICENSE.md) for more information. 84 | 85 | © 2021 [Marcus Olsson](https://marcusolsson.me). 86 | 87 | [ico-version]: https://img.shields.io/packagist/v/olssonm/laravel-backup-shield.svg?style=flat-square 88 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 89 | [ico-build]: https://img.shields.io/github/workflow/status/olssonm/laravel-backup-shield/Run%20tests.svg?style=flat-square&label=tests 90 | [ico-downloads]: https://img.shields.io/packagist/dt/olssonm/laravel-backup-shield.svg?style=flat-square 91 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/g/olssonm/laravel-backup-shield.svg?style=flat-square 92 | [link-packagist]: https://packagist.org/packages/olssonm/laravel-backup-shield 93 | [link-build]: https://github.com/olssonm/laravel-backup-shield/actions?query=workflow%3A%22Run+tests%22 94 | [link-scrutinizer]: https://scrutinizer-ci.com/g/olssonm/laravel-backup-shield 95 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "olssonm/laravel-backup-shield", 3 | "description": "Protection for your laravel backups", 4 | "keywords": [ 5 | "olssonm", 6 | "laravel", 7 | "backup", 8 | "password", 9 | "security" 10 | ], 11 | "homepage": "https://github.com/olssonm/laravel-backup-shield", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Marcus Olsson", 16 | "email": "contact@marcusolsson.me", 17 | "homepage": "https://marcusolsson.me" 18 | } 19 | ], 20 | "require": { 21 | "php": "^7.3|^8.0", 22 | "ext-zip": "^1.14.0", 23 | "illuminate/support": "^6.0|^7.0|^8.0", 24 | "spatie/laravel-backup": "~6.0|~7.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^8.0|^9.0", 28 | "orchestra/testbench": "^4.0|^5.0|^6.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Olssonm\\BackupShield\\": "src" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Olssonm\\BackupShield\\Tests\\": "tests" 38 | } 39 | }, 40 | "scripts": { 41 | "test": "vendor/bin/phpunit" 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "3.x-dev" 46 | }, 47 | "laravel": { 48 | "providers": [ 49 | "Olssonm\\BackupShield\\BackupShieldServiceProvider" 50 | ] 51 | } 52 | }, 53 | "sort-packages": true, 54 | "minimum-stability": "stable" 55 | } 56 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BackupShieldServiceProvider.php: -------------------------------------------------------------------------------- 1 | config = __DIR__ . '/config/backup-shield.php'; 29 | 30 | parent::__construct($app); 31 | } 32 | 33 | /** 34 | * Perform post-registration booting of services. 35 | * 36 | * @return void 37 | */ 38 | public function boot() : void 39 | { 40 | // Publishing of configuration 41 | $this->publishes([ 42 | $this->config => config_path('backup-shield.php'), 43 | ]); 44 | 45 | // Listen for the "BackupZipWasCreated" event 46 | Event::listen( 47 | BackupZipWasCreated::class, 48 | PasswordProtectZip::class 49 | ); 50 | 51 | parent::boot(); 52 | } 53 | 54 | /** 55 | * Register any package services. 56 | * 57 | * @return void 58 | */ 59 | public function register() : void 60 | { 61 | $this->mergeConfigFrom( 62 | $this->config, 'backup-shield' 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Encryption.php: -------------------------------------------------------------------------------- 1 | password = config('backup-shield.password'); 34 | 35 | // If no password is set, just return the backup-path 36 | if (!$this->password) { 37 | return $this->path = $path; 38 | } 39 | 40 | consoleOutput()->info('Applying password and encryption to zip using ZipArchive...'); 41 | 42 | $this->makeZip($path); 43 | 44 | consoleOutput()->info('Successfully applied password and encryption to zip.'); 45 | } 46 | 47 | /** 48 | * Use native PHP ZipArchive 49 | * 50 | * @return void 51 | */ 52 | protected function makeZip(string $path): void 53 | { 54 | $encryption = config('backup-shield.encryption'); 55 | 56 | $zipArchive = new ZipArchive; 57 | 58 | $zipArchive->open($path, ZipArchive::OVERWRITE); 59 | $zipArchive->addFile($path, 'backup.zip'); 60 | $zipArchive->setPassword($this->password); 61 | Collection::times($zipArchive->numFiles, function($i) use ($zipArchive, $encryption) { 62 | $zipArchive->setEncryptionIndex($i - 1, $encryption); 63 | }); 64 | $zipArchive->close(); 65 | 66 | $this->path = $path; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Listeners/PasswordProtectZip.php: -------------------------------------------------------------------------------- 1 | pathToZip))->path; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/config/backup-shield.php: -------------------------------------------------------------------------------- 1 | env('APP_KEY'), 5 | 'encryption' => \Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT 6 | 7 | // Available encryption methods: 8 | // \Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT (AES 128) 9 | // \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_128 (AES 128) 10 | // \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_192 (AES 192) 11 | // \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_256 (AES 256) 12 | ]; 13 | -------------------------------------------------------------------------------- /tests/BackupShieldTests.php: -------------------------------------------------------------------------------- 1 | 'Olssonm\BackupShield\BackupShieldServiceProvider' 38 | ]); 39 | 40 | $output = Artisan::output(); 41 | $test1 = false; 42 | $test2 = false; 43 | 44 | if(strpos($output, 'Copied File') !== false) { 45 | $test1 = true; 46 | } 47 | 48 | if(strpos($output, 'laravel-backup-shield/src/config/backup-shield.php') !== false) { 49 | $test2 = true; 50 | } 51 | 52 | $this->assertTrue($test1); 53 | $this->assertTrue($test2); 54 | } 55 | 56 | /** 57 | * @depends testConfigFileIsInstalled 58 | */ 59 | public function testListenerReturnData() 60 | { 61 | // Set parameters for testing 62 | $path = __DIR__ . '/resources/test.zip'; 63 | $pathTest = __DIR__ . '/resources/processed.zip'; 64 | 65 | // Make file ready for testing 66 | copy($path, $pathTest); 67 | 68 | // Manually set config 69 | config()->set('backup-shield.password', self::$password); 70 | config()->set('backup-shield.encryption', \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_256); 71 | 72 | // Fire event 73 | $data = event(new BackupZipWasCreated($pathTest)); 74 | 75 | $this->assertEquals($pathTest, $data[0]); 76 | } 77 | 78 | /** 79 | * @depends testListenerReturnData 80 | */ 81 | public function testCorrectEncrypterEngine() 82 | { 83 | $path = __DIR__ . '/resources/processed.zip'; 84 | 85 | $stat = null; 86 | $zip = new ZipArchive; 87 | 88 | if ($zip->open($path) === true) { 89 | $stat = $zip->statIndex(0); 90 | $zip->close(); 91 | } else { 92 | throw Exception('Could not open .zip-file'); 93 | } 94 | 95 | $this->assertEquals('backup.zip', $stat['name']); 96 | $this->assertEquals(259, $stat['encryption_method']); // AES 256 97 | $this->assertEquals(8, $stat['comp_method']); 98 | } 99 | 100 | /** 101 | * @depends testListenerReturnData 102 | */ 103 | public function testCorrectPasswordProtection() 104 | { 105 | $path = __DIR__ . '/resources/processed.zip'; 106 | 107 | $zip = new ZipArchive; 108 | 109 | if ($zip->open($path) === true) { 110 | $zip->setPassword(self::$password); 111 | $result = $zip->extractTo(__DIR__ . '/resources/extraction'); 112 | $this->assertTrue($result); 113 | $zip->close(); 114 | } else { 115 | throw Exception('Could not open .zip-file'); 116 | } 117 | } 118 | 119 | /** 120 | * @depends testListenerReturnData 121 | */ 122 | public function testIncorrectPasswordProtection() 123 | { 124 | $path = __DIR__ . '/resources/processed.zip'; 125 | 126 | $zip = new ZipArchive; 127 | 128 | if ($zip->open($path) === true) { 129 | $zip->setPassword('b4dp4$$w0rd'); 130 | $result = $zip->extractTo(__DIR__ . '/resources/extraction'); 131 | $this->assertFalse($result); 132 | $zip->close(); 133 | } else { 134 | throw Exception('Could not open .zip-file'); 135 | } 136 | } 137 | 138 | public function testRetrieveEncryptionConstants() 139 | { 140 | $encryption = new \Olssonm\BackupShield\Encryption(); 141 | 142 | $this->assertEquals('257', \Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT); 143 | $this->assertEquals('257', \Olssonm\BackupShield\Encryption::ENCRYPTION_DEFAULT); 144 | $this->assertEquals('258', \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_192); 145 | $this->assertEquals('259', \Olssonm\BackupShield\Encryption::ENCRYPTION_WINZIP_AES_256); 146 | } 147 | 148 | /** Teardown */ 149 | public static function tearDownAfterClass(): void 150 | { 151 | // Delete config and test-files 152 | $processedFile = __DIR__ . '/resources/processed.zip'; 153 | if (file_exists($processedFile)) { 154 | unlink($processedFile); 155 | } 156 | 157 | $configTestFile = __DIR__ . '/../vendor/orchestra/testbench-core/laravel/config/backup-shield.php'; 158 | if (file_exists($configTestFile)) { 159 | unlink($configTestFile); 160 | } 161 | 162 | $configTestFileAlt = __DIR__ . '/../vendor/orchestra/testbench-core/fixture/config/backup-shield.php'; 163 | if (file_exists($configTestFileAlt)) { 164 | unlink($configTestFileAlt); 165 | } 166 | 167 | // Delete extracted files 168 | array_map('unlink', glob(__DIR__ . "/resources/extraction/*.*")); 169 | rmdir(__DIR__ . "/resources/extraction"); 170 | 171 | parent::tearDownAfterClass(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tests/resources/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olssonm/laravel-backup-shield/456467e7f506555be3547aeffbf6ca3fc630f233/tests/resources/test.zip --------------------------------------------------------------------------------