Remove totally the SQLite support

- Run all the migrations in the MySQL database
- Add foreign keys + cascade support
- Remove the AccountDeleting event (now useless)
- Simplify the related code
- Keep (for now), the FlexiSIP structure to ensure compatibility
- Update the README
- Update the test suite
This commit is contained in:
Timothée Jaussoin 2021-06-02 12:08:50 +02:00
parent 2ed044aab1
commit fe279e3244
32 changed files with 96 additions and 177 deletions

View file

@ -28,16 +28,13 @@ PRIVACY_POLICY_URL= # A URL pointing to the Privacy Policy
LOG_CHANNEL=stack
# Local FlexiAPI database, deprecated, the external database is used
# DB_DATABASE=/var/www/flexiapi/db.sqlite
# External FlexiSIP database
DB_EXTERNAL_DRIVER=mysql
DB_EXTERNAL_HOST=127.0.0.1
DB_EXTERNAL_PORT=3306
DB_EXTERNAL_DATABASE=flexisip
DB_EXTERNAL_USERNAME=flexisip
DB_EXTERNAL_PASSWORD=flexisip
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=flexisip
DB_USERNAME=flexisip
DB_PASSWORD=flexisip
# Logs
BROADCAST_DRIVER=log

View file

@ -17,17 +17,10 @@ Clone the repository, install the dependencies and generate a key.
composer install --no-dev
php artisan key:generate
Then configure the database connection in the `.env` file (from the `.env.example` one). And migrate the tables.
Then configure the database connection in the `.env` file (from the `.env.example` one). And migrate the tables. The migration *MUST* be run on an empty database.
php artisan migrate
FlexiAPI can store its local tables in a separate SQLite database. To enable it uncomment and complete the following configuration in the .env file
# DB_DRIVER=sqlite
# DB_DATABASE=/home/edhelas/Repositories/flexisip-account-manager/flexiapi/storage/db.sqlite
By default both local and remote data are stored in the Flexisip account database. You can configure it using the `DB_EXTERNAL_*` parameters in the `.env` file.
You can also run the test suit using `phpunit`.
To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/8.x).
@ -100,11 +93,7 @@ Allow the webserver user to write in the `storage/` directory:
chcon -R -t httpd_sys_rw_content_t storage/
If you have your SQLite DB setup in another directory don't forget to allow write rights as well
chcon -R -t httpd_sys_rw_content_t db.sqlite
If your external database is locate on a remote machine, you should also allow your webserver user to connect to remote hosts:
If your database is located on a remote machine, you should also allow your webserver user to connect to remote hosts:
semanage port -a -t http_port_t -p tcp 3306 // Open remote connections on the MySQL port for example
setsebool httpd_can_network_connect 1 // Allow remote network connected

View file

@ -29,14 +29,12 @@ use App\ApiKey;
use App\Password;
use App\EmailChanged;
use App\Helpers\Utils;
use App\Events\AccountDeleting;
use App\Mail\ChangingEmail;
class Account extends Authenticatable
{
use HasFactory;
protected $connection = 'external';
protected $with = ['passwords', 'admin', 'emailChanged', 'alias', 'activationExpiration'];
protected $hidden = ['alias', 'expire_time', 'confirmation_key'];
protected $dateTimes = ['creation_time'];
@ -46,12 +44,6 @@ class Account extends Authenticatable
];
public $timestamps = false;
protected $dispatchesEvents = [
// Remove all the related data, accross multiple database
// and without foreign-keys (sic)
'deleting' => AccountDeleting::class,
];
protected static function booted()
{
static::addGlobalScope('domain', function (Builder $builder) {

View file

@ -10,7 +10,6 @@ class ActivationExpiration extends Model
{
use HasFactory;
protected $connection = 'local';
protected $casts = [
'expires' => 'datetime:Y-m-d H:i:s',
];

View file

@ -26,7 +26,6 @@ class Admin extends Model
{
use HasFactory;
protected $connection = 'local';
protected $table = 'admins';
protected $hidden = ['id', 'account_id'];

View file

@ -24,7 +24,6 @@ use Illuminate\Database\Eloquent\Model;
class Alias extends Model
{
protected $table = 'aliases';
protected $connection = 'external';
public $timestamps = false;
public function account()

View file

@ -26,7 +26,6 @@ class ApiKey extends Model
{
use HasFactory;
protected $connection = 'local';
protected $table = 'api_keys';
public function account()

View file

@ -23,7 +23,6 @@ use Illuminate\Database\Eloquent\Model;
class DigestNonce extends Model
{
protected $connection = 'local';
protected $table = 'nonces';
public function account()

View file

@ -23,7 +23,6 @@ use Illuminate\Database\Eloquent\Model;
class EmailChanged extends Model
{
protected $connection = 'local';
protected $table = 'email_changed';
protected $hidden = ['id', 'updated_at', 'hash', 'account_id'];
protected $casts = [

View file

@ -1,25 +0,0 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Account;
class AccountDeleting
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(Account $account)
{
$account->alias()->delete();
$account->passwords()->delete();
$account->activationExpiration()->delete();
$account->nonces()->delete();
$account->admin()->delete();
$account->apiKey()->delete();
$account->emailChanged()->delete();
}
}

View file

@ -91,10 +91,10 @@ class AuthenticateController extends Controller
public function authenticateEmail(Request $request)
{
$request->validate([
'email' => 'required|email|exists:external.accounts,email',
'email' => 'required|email|exists:accounts,email',
'username' => [
'required',
Rule::exists('external.accounts', 'username')->where(function ($query) use ($request) {
Rule::exists('accounts', 'username')->where(function ($query) use ($request) {
$query->where('email', $request->get('email'));
}),
],

View file

@ -68,7 +68,7 @@ class RegisterController extends Controller
'privacy' => 'accepted',
'username' => [
'required',
Rule::unique('external.accounts', 'username')->where(function ($query) use ($request) {
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
$query->where('domain', config('app.sip_domain'));
}),
'filled',
@ -107,15 +107,15 @@ class RegisterController extends Controller
'terms' =>'accepted',
'privacy' => 'accepted',
'username' => [
Rule::unique('external.accounts', 'username')->where(function ($query) use ($request) {
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
$query->where('domain', config('app.sip_domain'));
}),
'nullable',
new WithoutSpaces
],
'phone' => [
'required', 'unique:external.aliases,alias',
'unique:external.accounts,username',
'required', 'unique:aliases,alias',
'unique:accounts,username',
new WithoutSpaces, 'starts_with:+'
],
'g-recaptcha-response' => 'required|captcha',

View file

@ -50,7 +50,7 @@ class AccountController extends Controller
$request->validate([
'username' => [
'required',
Rule::unique('external.accounts', 'username')->where(function ($query) use ($request) {
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
$query->where('domain', config('app.sip_domain'));
}),
'filled',

View file

@ -35,8 +35,8 @@ class AccountPhoneController extends Controller
{
$request->validate([
'phone' => [
'required', 'unique:external.aliases,alias',
'unique:external.accounts,username',
'required', 'unique:aliases,alias',
'unique:accounts,username',
new WithoutSpaces, 'starts_with:+'
]
]);

View file

@ -73,7 +73,7 @@ class AccountController extends Controller
$request->validate([
'username' => [
'required',
Rule::unique('external.accounts', 'username')->where(function ($query) use ($request) {
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
$query->where('domain', config('app.sip_domain'));
}),
'filled',
@ -89,8 +89,8 @@ class AccountController extends Controller
'nullable',
],
'phone' => [
'unique:external.aliases,alias',
'unique:external.accounts,username',
'unique:aliases,alias',
'unique:accounts,username',
new WithoutSpaces, 'starts_with:+'
]
]);

View file

@ -26,7 +26,6 @@ class Password extends Model
{
use HasFactory;
protected $connection = 'external';
public $timestamps = false;
protected $hidden = ['id', 'password', 'account_id', 'created_at', 'updated_at'];

View file

@ -9,8 +9,6 @@ class PhoneChangeCode extends Model
{
use HasFactory;
protected $connection = 'local';
public function account()
{
return $this->belongsTo('App\Account');

View file

@ -32,7 +32,7 @@ return [
|
*/
'default' => 'local',
'default' => 'mysql',
/*
|--------------------------------------------------------------------------
@ -52,31 +52,17 @@ return [
'connections' => [
'local' => [
'driver' => env('DB_DRIVER', env('DB_EXTERNAL_DRIVER', 'mysql')),
'mysql' => [
'driver' => env('DB_DRIVER', 'mysql'),
'url' => env('DATABASE_URL'),
'host' => env('DB_EXTERNAL_HOST', '127.0.0.1'),
'port' => env('DB_EXTERNAL_PORT', '3306'),
'database' => env('DB_DATABASE', env('DB_EXTERNAL_DATABASE', database_path('database.sqlite'))),
'username' => env('DB_EXTERNAL_USERNAME', 'forge'),
'password' => env('DB_EXTERNAL_PASSWORD', ''),
'charset' => env('DB_EXTERNAL_DRIVER', 'mysql') == 'mysql' ? 'utf8mb4' : null,
'collation' => env('DB_EXTERNAL_DRIVER', 'mysql') == 'mysql' ? 'utf8mb4_unicode_ci' : null,
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'external' => [
'driver' => env('DB_EXTERNAL_DRIVER', 'mysql'),
'url' => env('DATABASE_URL'),
'host' => env('DB_EXTERNAL_HOST', '127.0.0.1'),
'port' => env('DB_EXTERNAL_PORT', '3306'),
'database' => env('DB_EXTERNAL_DATABASE', 'forge'),
'username' => env('DB_EXTERNAL_USERNAME', 'forge'),
'password' => env('DB_EXTERNAL_PASSWORD', ''),
'unix_socket' => env('DB_EXTERNAL_SOCKET', ''),
'charset' => env('DB_EXTERNAL_DRIVER', 'mysql') == 'mysql' ? 'utf8mb4' : null,
'collation' => env('DB_EXTERNAL_DRIVER', 'mysql') == 'mysql' ? 'utf8mb4_unicode_ci' : null,
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,

View file

@ -8,7 +8,7 @@ class CreateUsersTable extends Migration
{
public function up()
{
Schema::connection('local')->create('users', function (Blueprint $table) {
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email', 160)->unique(); // Because we (still) need to support MySQL 5.5 and its 767 bytes limit ¯\_(ツ)_/¯

View file

@ -8,7 +8,7 @@ class CreatePasswordResetsTable extends Migration
{
public function up()
{
Schema::connection('local')->create('password_resets', function (Blueprint $table) {
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
@ -17,6 +17,6 @@ class CreatePasswordResetsTable extends Migration
public function down()
{
Schema::connection('local')->dropIfExists('password_resets');
Schema::dropIfExists('password_resets');
}
}

View file

@ -25,7 +25,7 @@ class CreateFailedJobsTable extends Migration
{
public function up()
{
Schema::connection('local')->create('failed_jobs', function (Blueprint $table) {
Schema::create('failed_jobs', function (Blueprint $table) {
$table->increments('id');
$table->text('connection');
$table->text('queue');
@ -37,6 +37,6 @@ class CreateFailedJobsTable extends Migration
public function down()
{
Schema::connection('local')->dropIfExists('failed_jobs');
Schema::dropIfExists('failed_jobs');
}
}

View file

@ -25,37 +25,33 @@ class CreateAccountsPasswordsTables extends Migration
{
public function up()
{
if (!Schema::connection('external')->hasTable('accounts')) {
Schema::connection('external')->create('accounts', function (Blueprint $table) {
$table->increments('id');
$table->string('username', 64);
$table->string('domain', 64);
$table->string('email', 64)->nullable();
$table->boolean('activated')->default(false);
$table->string('confirmation_key', 14)->nullable();
$table->string('ip_address', 39);
$table->string('user_agent', 256);
$table->datetime('creation_time');
$table->datetime('expire_time')->nullable();
});
}
Schema::create('accounts', function (Blueprint $table) {
$table->increments('id');
$table->string('username', 64);
$table->string('domain', 64);
$table->string('email', 64)->nullable();
$table->boolean('activated')->default(false);
$table->string('confirmation_key', 14)->nullable();
$table->string('ip_address', 39);
$table->string('user_agent', 256);
$table->datetime('creation_time');
$table->datetime('expire_time')->nullable();
});
if (!Schema::connection('external')->hasTable('passwords')) {
Schema::connection('external')->create('passwords', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned();
$table->string('password', 255);
$table->string('algorithm', 10)->default('MD5');
Schema::create('passwords', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned();
$table->string('password', 255);
$table->string('algorithm', 10)->default('MD5');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
//Schema::connection('external')->dropIfExists('passwords');
//Schema::connection('external')->dropIfExists('accounts');
Schema::dropIfExists('passwords');
Schema::dropIfExists('accounts');
}
}

View file

@ -25,20 +25,20 @@ class CreateNoncesDigestTable extends Migration
{
public function up()
{
Schema::connection('local')->create('nonces', function (Blueprint $table) {
Schema::create('nonces', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned();
$table->string('nonce');
$table->integer('nc')->default(0);
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('nonces');
Schema::dropIfExists('nonces');
}
}

View file

@ -25,18 +25,18 @@ class CreateAdminsTable extends Migration
{
public function up()
{
Schema::connection('local')->create('admins', function (Blueprint $table) {
Schema::create('admins', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned();
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('admins');
Schema::dropIfExists('admins');
}
}

View file

@ -25,20 +25,20 @@ class AddEmailChangedTable extends Migration
{
public function up()
{
Schema::connection('local')->create('email_changed', function (Blueprint $table) {
Schema::create('email_changed', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned()->unique();
$table->string('new_email');
$table->string('hash');
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('email_changed');
Schema::dropIfExists('email_changed');
}
}

View file

@ -25,19 +25,19 @@ class CreateApiKeysTable extends Migration
{
public function up()
{
Schema::connection('local')->create('api_keys', function (Blueprint $table) {
Schema::create('api_keys', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned()->unique();
$table->string('key', 160)->unique(); // MySQL 5.5 limit…
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('api_keys');
Schema::dropIfExists('api_keys');
}
}

View file

@ -8,22 +8,20 @@ class CreateAliasesTable extends Migration
{
public function up()
{
if (!Schema::connection('external')->hasTable('aliases')) {
Schema::connection('external')->create('aliases', function (Blueprint $table) {
$table->id();
Schema::create('aliases', function (Blueprint $table) {
$table->id();
$table->integer('account_id')->unsigned();
$table->string('alias', 64);
$table->string('domain', 64);
$table->integer('account_id')->unsigned();
$table->string('alias', 64);
$table->string('domain', 64);
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
//Schema::dropIfExists('aliases');
Schema::dropIfExists('aliases');
}
}

View file

@ -8,20 +8,20 @@ class AddPhoneChangeCodesTable extends Migration
{
public function up()
{
Schema::connection('local')->create('phone_change_codes', function (Blueprint $table) {
Schema::create('phone_change_codes', function (Blueprint $table) {
$table->increments('id');
$table->integer('account_id')->unsigned();
$table->string('code');
$table->string('phone');
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('phone_change_codes');
Schema::dropIfExists('phone_change_codes');
}
}

View file

@ -8,7 +8,7 @@ class CreateTokensTable extends Migration
{
public function up()
{
Schema::connection('local')->create('tokens', function (Blueprint $table) {
Schema::create('tokens', function (Blueprint $table) {
$table->id();
$table->string('token');
$table->string('pn_provider');
@ -24,6 +24,6 @@ class CreateTokensTable extends Migration
public function down()
{
Schema::connection('local')->dropIfExists('tokens');
Schema::dropIfExists('tokens');
}
}

View file

@ -8,19 +8,19 @@ class CreateActivationExpirationsTable extends Migration
{
public function up()
{
Schema::connection('local')->create('activation_expirations', function (Blueprint $table) {
Schema::create('activation_expirations', function (Blueprint $table) {
$table->id();
$table->integer('account_id')->unsigned();
$table->dateTime('expires');
$table->timestamps();
//$table->foreign('account_id')->references('id')
// ->on('accounts')->onDelete('cascade');
$table->foreign('account_id')->references('id')
->on('accounts')->onDelete('cascade');
});
}
public function down()
{
Schema::connection('local')->dropIfExists('activation_expirations');
Schema::dropIfExists('activation_expirations');
}
}

View file

@ -17,8 +17,8 @@
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_EXTERNAL_DRIVER" value="sqlite"/>
<server name="DB_EXTERNAL_DATABASE" value=":memory:"/>
<server name="DB_DRIVER" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>

View file

@ -8,7 +8,7 @@
#%define _datadir %{_datarootdir}
#%define _docdir %{_datadir}/doc
%define build_number 76
%define build_number 77
%define var_dir /var/opt/belledonne-communications
%define opt_dir /opt/belledonne-communications/share/flexisip-account-manager
@ -91,8 +91,6 @@ cp -R conf/* "$RPM_BUILD_ROOT/etc/flexisip-account-manager/"
mkdir -p %{var_dir}/flexiapi/storage/framework/testing
mkdir -p %{var_dir}/flexiapi/storage/framework/views
mkdir -p %{var_dir}/flexiapi/bootstrap/cache
touch %{var_dir}/flexiapi/storage/db.sqlite
touch %{var_dir}/flexiapi/storage/external.db.sqlite
mkdir -p %{var_dir}/log
touch %{var_dir}/log/account-manager.log
@ -119,9 +117,6 @@ cp -R conf/* "$RPM_BUILD_ROOT/etc/flexisip-account-manager/"
if ! test -f %{env_config_file}; then
cd %{opt_dir}/flexiapi/
cp .env.example %{env_config_file}
sed -i 's/DB_DATABASE=.*/DB_DATABASE=\/var\/opt\/belledonne-communications\/flexiapi\/storage\/db.sqlite/g' %{env_config_file}
sed -i 's/DB_EXTERNAL_DRIVER=.*/DB_EXTERNAL_DRIVER=sqlite/g' %{env_config_file}
sed -i 's/DB_EXTERNAL_DATABASE=.*/DB_EXTERNAL_DATABASE=\/var\/opt\/belledonne-communications\/flexiapi\/storage\/external.db.sqlite/g' %{env_config_file}
ln -s %{env_config_file} %{env_symlink_file}