From 6bb18a2b9579755939d1f5ea0ecb8d6412a69102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= <33033094+stancl@users.noreply.github.com> Date: Thu, 7 Feb 2019 15:26:42 +0100 Subject: [PATCH] Fix Travis, add docs for generating HTTPS certificates (#19) --- .travis.yml | 8 ++++++- README.md | 38 ++++++++++++++++++++++++++++++++ phpunit.xml | 5 ++++- src/DatabaseManager.php | 5 +++++ src/TenancyServiceProvider.php | 2 ++ src/TenantManager.php | 19 ++++++++++++++-- src/Traits/BootstrapsTenancy.php | 6 +++-- tests/TestCase.php | 32 ++++++++++++++++++++++++++- 8 files changed, 108 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1433d9ac..88e84dda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,18 @@ language: php php: - - '7.1' - '7.2' + - '7.1' branches: only: - master +services: + - redis-server + +before_install: + - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + install: - travis_retry composer install --no-interaction diff --git a/README.md b/README.md index 484c54bc..c05add20 100644 --- a/README.md +++ b/README.md @@ -393,3 +393,41 @@ Tenant migrations are located in `database/migrations/tenant`, so you should mov ## Some tips - If you create a tenant using the interactive console (`artisan tinker`) and use sqlite, you might need to change the database's permissions and/or ownership (`chmod`/`chown`) so that the web application can access it. + +## HTTPS certificates + +HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate. + +If you use the model where second level domains are used, there are multiple ways you can solve this. + +This guide focuses on nginx. + +### 1. Use nginx with the lua module + +Specifically, you're interested in the [`ssl_certificate_by_lua_block`](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) directive. Nginx doesn't support using variables such as the hostname in the `ssl_certificate` directive, which is why the lua module is needed. + +This approach lets you use one server block for all tenants. + +### 2. Add a simple server block for each tenant + +You can store most of your config in a file, such as `/etc/nginx/includes/tenant`, and include this file into tenant server blocks. + +```nginx +server { + include includes/tenant; + server_name foo.bar; + # ssl_certificate /etc/foo/...; +} +``` + +### Generating certificates + +You can generate a certificate using certbot. If you use the `--nginx` flag, you will need to run certbot as root. If you use the `--webroot` flag, you only need the user that runs it to have write access to the webroot directory (or perhaps webroot/.well-known is enough) and some certbot files (you can specify these using --work-dir, --config-dir and --logs-dir). + +Creating this config dynamically from PHP is not easy, but is probably feasible. Giving `www-data` write access to `/etc/nginx/sites-available/tenants.conf` should work. + +However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP. + +## Testing + +If you run the tests of this package, please make sure you don't store anything in Redis @ 127.0.0.1:6379 db#14. The contents of this database are flushed everytime the tests are run. diff --git a/phpunit.xml b/phpunit.xml index 2e630c0b..da25c19f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -15,7 +15,10 @@ - ./app + ./src + + ./src/routes.php + diff --git a/src/DatabaseManager.php b/src/DatabaseManager.php index 745efddf..f30025fe 100644 --- a/src/DatabaseManager.php +++ b/src/DatabaseManager.php @@ -44,6 +44,11 @@ class DatabaseManager return DB::statement("CREATE DATABASE `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); } + public function delete() + { + // todo: delete database. similar to create() + } + public function getDriver(): ?string { return config("database.connections.tenant.driver"); diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 678ebe24..f613184b 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Stancl\Tenancy\Commands\TenantList; use Stancl\Tenancy\Interfaces\StorageDriver; +use Stancl\Tenancy\Interfaces\ServerConfigManager; use Stancl\Tenancy\StorageDrivers\RedisStorageDriver; class TenancyServiceProvider extends ServiceProvider @@ -54,6 +55,7 @@ class TenancyServiceProvider extends ServiceProvider $this->mergeConfigFrom(__DIR__ . '/config/tenancy.php', 'tenancy'); $this->app->bind(StorageDriver::class, $this->app['config']['tenancy.storage_driver']); + $this->app->bind(ServerConfigManager::class, $this->app['config']['tenancy.server.manager']); $this->app->singleton(DatabaseManager::class); $this->app->singleton(TenantManager::class, function ($app) { return new TenantManager($app, $app[StorageDriver::class], $app[DatabaseManager::class]); diff --git a/src/TenantManager.php b/src/TenantManager.php index 75568367..57c66c04 100644 --- a/src/TenantManager.php +++ b/src/TenantManager.php @@ -2,9 +2,9 @@ namespace Stancl\Tenancy; -use Stancl\Tenancy\BootstrapsTenancy; use Illuminate\Support\Facades\Redis; use Stancl\Tenancy\Interfaces\StorageDriver; +use Stancl\Tenancy\Traits\BootstrapsTenancy; class TenantManager { @@ -77,7 +77,7 @@ class TenantManager throw new \Exception("Domain $domain is already occupied by tenant $id."); } - $tenant = $this->storage->createTenant($domain, \Uuid::generate(1, $domain)); + $tenant = $this->storage->createTenant($domain, \Webpatser\Uuid\Uuid::generate(1, $domain)); $this->database->create($this->getDatabaseName($tenant)); return $tenant; @@ -264,4 +264,19 @@ class TenantManager return $this->put($this->put($key, $value)); } + + /** + * Return the identified tenant's attribute(s). + * + * @param string $attribute + * @return mixed + */ + public function __invoke($attribute) + { + if (is_null($attribute)) { + return $this->tenant; + } + + return $this->tenant[(string) $attribute]; + } } diff --git a/src/Traits/BootstrapsTenancy.php b/src/Traits/BootstrapsTenancy.php index 1775fc15..a791854a 100644 --- a/src/Traits/BootstrapsTenancy.php +++ b/src/Traits/BootstrapsTenancy.php @@ -1,6 +1,9 @@ tenant['uuid']; $client = Redis::connection($connection)->client(); diff --git a/tests/TestCase.php b/tests/TestCase.php index a33d3ad7..1a29c5b8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,8 @@ namespace Stancl\Tenancy\Tests; +use Illuminate\Support\Facades\Redis; + class TestCase extends \Orchestra\Testbench\TestCase { /** @@ -13,7 +15,35 @@ class TestCase extends \Orchestra\Testbench\TestCase { parent::setUp(); - // + Redis::connection('tenancy')->flushdb(); + + tenant()->create('localhost'); + + tenancy()->init('localhost'); + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function getEnvironmentSetUp($app) + { + $app['config']->set('database.redis.client', 'phpredis'); + $app['config']->set('database.redis.tenancy', [ + 'host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'), + 'password' => env('TENANCY_TEST_REDIS_PASSWORD', null), + 'port' => env('TENANCY_TEST_REDIS_PORT', 6379), + // Use the #14 Redis database unless specified otherwise. + // Make sure you don't store anything in this db! + 'database' => env('TENANCY_TEST_REDIS_DB', 14), + ]); + $app['config']->set('tenancy.database', [ + 'based_on' => 'sqlite', + 'prefix' => 'tenant', + 'suffix' => '.sqlite', + ]); } protected function getPackageProviders($app)