From 2ed4f02c113b3ecefc2621c1eecd4ed8364bfa25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Mon, 1 Jul 2024 14:11:47 +0000 Subject: [PATCH] Fix FLEXIAPI-182 Replace APP_SUPER_ADMINS_SIP_DOMAINS with a proper sip_domains table, API endpoints, UI panels, tests and documentation --- CHANGELOG.md | 1 + README.md | 6 + flexiapi/.env.example | 1 - flexiapi/app/Account.php | 15 +-- .../app/Console/Commands/ClearApiKeys.php | 4 +- flexiapi/app/Console/Commands/ClearNonces.php | 2 +- .../Commands/ClearOldAccountsTombstones.php | 5 +- .../Console/Commands/CreateAdminAccount.php | 17 ++- .../Commands/CreateAdminAccountTest.php | 8 +- .../app/Console/Commands/CreateSipDomain.php | 50 ++++++++ .../Commands/RemoveUnconfirmedAccounts.php | 2 +- flexiapi/app/Device.php | 1 + .../Controllers/Admin/AccountController.php | 13 +- .../Controllers/Admin/SipDomainController.php | 94 ++++++++++++++ .../Api/Account/AccountController.php | 1 + .../Api/Account/DeviceController.php | 7 +- .../Api/Admin/AccountController.php | 18 ++- .../Api/Admin/DeviceController.php | 7 +- .../Api/Admin/SipDomainController.php | 71 +++++++++++ .../Api/Admin/VcardsStorageController.php | 17 +++ flexiapi/app/Http/Kernel.php | 1 + .../app/Http/Middleware/AuthenticateAdmin.php | 24 ++-- .../Middleware/AuthenticateDigestOrKey.php | 7 -- .../app/Http/Middleware/AuthenticateJWT.php | 17 +++ .../Middleware/AuthenticateSuperAdmin.php | 34 +++++ .../Account/Create/Api/AsAdminRequest.php | 5 + .../Http/Requests/Account/Create/Request.php | 1 + .../Http/Requests/Account/Update/Request.php | 1 + flexiapi/app/Libraries/FlexisipConnector.php | 2 - flexiapi/app/SipDomain.php | 23 ++++ flexiapi/composer.phar | Bin 2989019 -> 2994611 bytes flexiapi/config/app.php | 1 - .../database/factories/AccountFactory.php | 20 ++- .../factories/ContactsListFactory.php | 17 +++ .../database/factories/SipDomainFactory.php | 42 +++++++ .../factories/StatisticsCallFactory.php | 17 +++ .../StatisticsMessageDeviceFactory.php | 17 +++ .../factories/StatisticsMessageFactory.php | 17 +++ flexiapi/database/factories/UserFactory.php | 1 + ..._06_11_090306_create_sip_domains_table.php | 40 ++++++ .../seeds/LiblinphoneTesterAccoutSeeder.php | 13 ++ .../views/account/device/index.blade.php | 57 +++++---- .../views/admin/account/create_edit.blade.php | 11 +- .../admin/account/device/index.blade.php | 25 ++-- .../admin/contacts_list/delete.blade.php | 16 ++- .../admin/sip_domain/create_edit.blade.php | 50 ++++++++ .../views/admin/sip_domain/delete.blade.php | 34 +++++ .../views/admin/sip_domain/index.blade.php | 43 +++++++ .../api/documentation_markdown.blade.php | 41 +++++- .../resources/views/parts/sidebar.blade.php | 4 + flexiapi/routes/api.php | 12 ++ flexiapi/routes/web.php | 7 ++ .../Feature/AccountJWTAuthenticationTest.php | 1 + .../tests/Feature/ApiAccountContactsTest.php | 2 +- flexiapi/tests/Feature/ApiAccountTest.php | 119 +++++++++++++----- .../Feature/ApiAccountVcardsStorageTest.php | 2 +- flexiapi/tests/Feature/ApiSipDomainTest.php | 117 +++++++++++++++++ 57 files changed, 1033 insertions(+), 148 deletions(-) create mode 100644 flexiapi/app/Console/Commands/CreateSipDomain.php create mode 100644 flexiapi/app/Http/Controllers/Admin/SipDomainController.php create mode 100644 flexiapi/app/Http/Controllers/Api/Admin/SipDomainController.php create mode 100644 flexiapi/app/Http/Middleware/AuthenticateSuperAdmin.php create mode 100644 flexiapi/app/SipDomain.php create mode 100644 flexiapi/database/factories/SipDomainFactory.php create mode 100644 flexiapi/database/migrations/2024_06_11_090306_create_sip_domains_table.php create mode 100644 flexiapi/resources/views/admin/sip_domain/create_edit.blade.php create mode 100644 flexiapi/resources/views/admin/sip_domain/delete.blade.php create mode 100644 flexiapi/resources/views/admin/sip_domain/index.blade.php create mode 100644 flexiapi/tests/Feature/ApiSipDomainTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d68409..293e19d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ v1.5 - Fix FLEXIAPI-185 Return null if the account dictionary is empty in the API - Fix FLEXIAPI-184 Append phone_change_code and email_change_code to the admin /accounts/ endpoint if they are available - Fix FLEXIAPI-183 Complete the account hooks on the dictionnary actions +- Fix FLEXIAPI-182 Replace APP_SUPER_ADMINS_SIP_DOMAINS with a proper sip_domains table, API endpoints, UI panels, console command, tests and documentation - Fix FLEXIAPI-181 Replace APP_ADMINS_MANAGE_MULTI_DOMAINS with APP_SUPER_ADMINS_SIP_DOMAINS - Fix FLEXIAPI-180 Fix the token and activation flow for the provisioning with token endpoint when the header is missing - Fix FLEXIAPI-179 Add Localization support as a Middleware that handles Accept-Language HTTP header diff --git a/README.md b/README.md index 2614ecb..7d42541 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,12 @@ FlexiAPI is also providing endpoints to provision Liblinphone powered devices. Y FlexiAPI is shipped with several console commands that you can launch using the `artisan` executable available at the root of this project. +### Create or update a SIP Domain + +Create or update a SIP Domain, required to then create accounts afterward. The `super` option enable/disable the domain as a super domain. + + php artisan sip_domains:create-update {domain} {--super} + ### Create an admin account Create an admin account, an API Key will also be generated along the way, it might expire after a while. diff --git a/flexiapi/.env.example b/flexiapi/.env.example index 42f8e7c..379d983 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -4,7 +4,6 @@ APP_KEY= APP_DEBUG=false APP_URL=http://localhost APP_SIP_DOMAIN=sip.example.com -APP_SUPER_ADMINS_SIP_DOMAINS= # A comma separated list of sip domains that has their admins super admins APP_LINPHONE_DAEMON_UNIX_PATH= APP_FLEXISIP_PUSHER_PATH= diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index a60a80c..9832b04 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -168,7 +168,6 @@ class Account extends Authenticatable return $this->hasMany(DigestNonce::class); } - public function passwords() { return $this->hasMany(Password::class); @@ -323,19 +322,7 @@ class Account extends Authenticatable public function getSuperAdminAttribute(): bool { - $domains = config('app.super_admins_sip_domains'); - - if (empty($domains)) { - return false; - } - - $domains = explode(',', $domains); - - if (empty($domains)) { - return false; - } - - return $this->admin && in_array($this->domain, $domains); + return SipDomain::where('domain', $this->domain)->where('super', true)->exists() && $this->admin; } /** diff --git a/flexiapi/app/Console/Commands/ClearApiKeys.php b/flexiapi/app/Console/Commands/ClearApiKeys.php index e23dd27..cb83dfd 100644 --- a/flexiapi/app/Console/Commands/ClearApiKeys.php +++ b/flexiapi/app/Console/Commands/ClearApiKeys.php @@ -19,11 +19,11 @@ namespace App\Console\Commands; -use App\ApiKey; use Illuminate\Console\Command; - use Carbon\Carbon; +use App\ApiKey; + class ClearApiKeys extends Command { protected $signature = 'accounts:clear-api-keys {minutes?}'; diff --git a/flexiapi/app/Console/Commands/ClearNonces.php b/flexiapi/app/Console/Commands/ClearNonces.php index 5788f7e..02e7fb7 100644 --- a/flexiapi/app/Console/Commands/ClearNonces.php +++ b/flexiapi/app/Console/Commands/ClearNonces.php @@ -20,8 +20,8 @@ namespace App\Console\Commands; use Illuminate\Console\Command; - use Carbon\Carbon; + use App\DigestNonce; class ClearNonces extends Command diff --git a/flexiapi/app/Console/Commands/ClearOldAccountsTombstones.php b/flexiapi/app/Console/Commands/ClearOldAccountsTombstones.php index c3e9f96..83e2e95 100644 --- a/flexiapi/app/Console/Commands/ClearOldAccountsTombstones.php +++ b/flexiapi/app/Console/Commands/ClearOldAccountsTombstones.php @@ -19,9 +19,10 @@ namespace App\Console\Commands; -use App\AccountTombstone; -use Carbon\Carbon; use Illuminate\Console\Command; +use Carbon\Carbon; + +use App\AccountTombstone; class ClearOldAccountsTombstones extends Command { diff --git a/flexiapi/app/Console/Commands/CreateAdminAccount.php b/flexiapi/app/Console/Commands/CreateAdminAccount.php index 2c62cb8..edb88ee 100644 --- a/flexiapi/app/Console/Commands/CreateAdminAccount.php +++ b/flexiapi/app/Console/Commands/CreateAdminAccount.php @@ -20,11 +20,10 @@ namespace App\Console\Commands; use Illuminate\Console\Command; -use Illuminate\Support\Str; +use Carbon\Carbon; use App\Account; -use App\ApiKey; -use Carbon\Carbon; +use App\SipDomain; class CreateAdminAccount extends Command { @@ -38,6 +37,8 @@ class CreateAdminAccount extends Command public function handle() { + $sipDomains = SipDomain::all('domain')->pluck('domain'); + $this->info('Your will create a new admin account in the database, existing accounts with the same credentials will be overwritten'); $username = $this->option('username'); @@ -49,7 +50,7 @@ class CreateAdminAccount extends Command } if (!$this->option('domain')) { - $domain = $this->ask('What will be the admin domain? Default: ' . config('app.sip_domain')); + $domain = $this->ask('What will be the admin domain? Default: ' . $sipDomains->first()); } if (!$this->option('password')) { @@ -57,9 +58,15 @@ class CreateAdminAccount extends Command } $username = $username ?? 'admin'; - $domain = $domain ?? config('app.sip_domain'); + $domain = $domain ?? $sipDomains->first(); $password = $password ?? 'change_me'; + if (!$sipDomains->contains($domain)) { + $this->error('The domain must be one of the following ones: ' . $sipDomains->implode(', ')); + $this->comment('You can create an extra domain using the dedicated console command'); + return Command::FAILURE; + } + // Delete the account if it already exists $account = Account::withoutGlobalScopes() ->where('username', $username) diff --git a/flexiapi/app/Console/Commands/CreateAdminAccountTest.php b/flexiapi/app/Console/Commands/CreateAdminAccountTest.php index 9c33216..dc02764 100644 --- a/flexiapi/app/Console/Commands/CreateAdminAccountTest.php +++ b/flexiapi/app/Console/Commands/CreateAdminAccountTest.php @@ -20,10 +20,11 @@ namespace App\Console\Commands; use Illuminate\Console\Command; +use Carbon\Carbon; use App\Account; use App\ApiKey; -use Carbon\Carbon; +use App\SipDomain; class CreateAdminAccountTest extends Command { @@ -40,6 +41,11 @@ class CreateAdminAccountTest extends Command $username = 'admin_test'; $domain = 'sip.example.org'; + $this->call('sip_domains:create-update', [ + 'domain' => $domain, + '--super' => 'true' + ]); + $this->call('accounts:create-admin-account', [ '--username' => $username, '--domain' => $domain, diff --git a/flexiapi/app/Console/Commands/CreateSipDomain.php b/flexiapi/app/Console/Commands/CreateSipDomain.php new file mode 100644 index 0000000..0544c35 --- /dev/null +++ b/flexiapi/app/Console/Commands/CreateSipDomain.php @@ -0,0 +1,50 @@ +. +*/ + +namespace App\Console\Commands; + +use App\SipDomain; +use Illuminate\Console\Command; + +class CreateSipDomain extends Command +{ + protected $signature = 'sip_domains:create-update {domain} {--super}'; + protected $description = 'Create a SIP Domain'; + + public function handle() + { + $this->info('Your will create or update a SIP Domain in the database'); + + $sipDomain = SipDomain::where('domain', $this->argument('domain'))->firstOrNew(); + $sipDomain->domain = $this->argument('domain'); + + $sipDomain->exists + ? $this->info('The domain already exists, updating it') + : $this->info('A new domain will be created'); + + $sipDomain->super = (bool)$this->option('super'); + $sipDomain->super + ? $this->info('Set as a super domain') + : $this->info('Set as a normal domain'); + + $sipDomain->save(); + + return Command::SUCCESS; + } +} diff --git a/flexiapi/app/Console/Commands/RemoveUnconfirmedAccounts.php b/flexiapi/app/Console/Commands/RemoveUnconfirmedAccounts.php index 938f8c7..a6c7f15 100644 --- a/flexiapi/app/Console/Commands/RemoveUnconfirmedAccounts.php +++ b/flexiapi/app/Console/Commands/RemoveUnconfirmedAccounts.php @@ -20,8 +20,8 @@ namespace App\Console\Commands; use Illuminate\Console\Command; - use Carbon\Carbon; + use App\Account; class RemoveUnconfirmedAccounts extends Command diff --git a/flexiapi/app/Device.php b/flexiapi/app/Device.php index a76aaa0..a6fa1f5 100644 --- a/flexiapi/app/Device.php +++ b/flexiapi/app/Device.php @@ -24,6 +24,7 @@ use Carbon\Carbon; class Device extends Model { + protected $fillable = ['user_agent']; public function fromRedisContact(string $contact) { preg_match("/<(.*)>;(.*)/", $contact, $matches); diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php index da8e993..7d1aea5 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php @@ -28,6 +28,7 @@ use App\ContactsList; use App\Http\Requests\Account\Create\Web\AsAdminRequest; use App\Http\Requests\Account\Update\Web\AsAdminRequest as WebAsAdminRequest; use App\Services\AccountService; +use App\SipDomain; class AccountController extends Controller { @@ -75,6 +76,9 @@ class AccountController extends Controller { return view('admin.account.create_edit', [ 'account' => new Account, + 'domains' => $request->user()?->superAdmin + ? SipDomain::all() + : SipDomain::where('domain', $request->user()->domain)->get(), 'protocols' => [null => 'None'] + Account::$dtmfProtocols ]); } @@ -88,11 +92,16 @@ class AccountController extends Controller return redirect()->route('admin.account.edit', $account->id); } - public function edit(int $accountId) + public function edit(Request $request, int $accountId) { + $account = Account::findOrFail($accountId); + return view('admin.account.create_edit', [ - 'account' => Account::findOrFail($accountId), + 'account' => $account, 'protocols' => [null => 'None'] + Account::$dtmfProtocols, + 'domains' => $request->user()?->superAdmin + ? SipDomain::all() + : SipDomain::where('domain', $account->domain)->get(), 'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($accountId) { $query->select('contacts_list_id') ->from('account_contacts_list') diff --git a/flexiapi/app/Http/Controllers/Admin/SipDomainController.php b/flexiapi/app/Http/Controllers/Admin/SipDomainController.php new file mode 100644 index 0000000..8f31a93 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/SipDomainController.php @@ -0,0 +1,94 @@ +. +*/ + +namespace App\Http\Controllers\Admin; + +use Illuminate\Http\Request; + +use App\Http\Controllers\Controller; +use App\SipDomain; +use Illuminate\Validation\Rule; + +class SipDomainController extends Controller +{ + public function index() + { + return view('admin.sip_domain.index', ['sip_domains' => SipDomain::withCount('accounts')->get()]); + } + + public function create() + { + return view('admin.sip_domain.create_edit', [ + 'sip_domain' => new SipDomain + ]); + } + + public function store(Request $request) + { + $request->validate([ + 'domain' => 'required|unique:sip_domains', + ]); + + $sipDomain = new SipDomain; + $sipDomain->domain = $request->get('domain'); + $sipDomain->super = $request->has('super') ? (bool)$request->get('super') == "true" : false; + $sipDomain->save(); + + return redirect()->route('admin.sip_domains.index'); + } + + public function edit(int $id) + { + return view('admin.sip_domain.create_edit', [ + 'sip_domain' => SipDomain::findOrFail($id) + ]); + } + + public function update(Request $request, int $id) + { + $sipDomain = SipDomain::findOrFail($id); + $sipDomain->super = $request->has('super') ? $request->get('super') == "true" : false; + $sipDomain->save(); + + return redirect()->route('admin.sip_domains.index'); + } + + public function delete(int $id) + { + return view('admin.sip_domain.delete', [ + 'sip_domain' => SipDomain::findOrFail($id) + ]); + } + + public function destroy(Request $request, int $id) + { + $sipDomain = SipDomain::findOrFail($id); + + $request->validate([ + 'domain' => [ + 'required', + Rule::in(['first-zone', $sipDomain->domain]), + ] + ]); + + $sipDomain->delete(); + + return redirect()->route('admin.sip_domains.index'); + } +} diff --git a/flexiapi/app/Http/Controllers/Api/Account/AccountController.php b/flexiapi/app/Http/Controllers/Api/Account/AccountController.php index 14edd79..53bc0ee 100644 --- a/flexiapi/app/Http/Controllers/Api/Account/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Account/AccountController.php @@ -43,6 +43,7 @@ use App\Rules\WithoutSpaces; use App\Rules\PasswordAlgorithm; use App\Services\AccountService; +use App\SipDomain; class AccountController extends Controller { diff --git a/flexiapi/app/Http/Controllers/Api/Account/DeviceController.php b/flexiapi/app/Http/Controllers/Api/Account/DeviceController.php index 27450d3..e3d18ad 100644 --- a/flexiapi/app/Http/Controllers/Api/Account/DeviceController.php +++ b/flexiapi/app/Http/Controllers/Api/Account/DeviceController.php @@ -20,16 +20,17 @@ namespace App\Http\Controllers\Api\Account; use App\Http\Controllers\Controller; -use Illuminate\Http\Request; use App\Libraries\FlexisipConnector; +use Illuminate\Http\Request; +use stdClass; class DeviceController extends Controller { public function index(Request $request) { - $connector = new FlexisipConnector; + $devices = (new FlexisipConnector)->getDevices($request->user()->identifier); - return $connector->getDevices($request->user()->identifier); + return ($devices->isEmpty()) ? new stdClass : $devices; } public function destroy(Request $request, string $uuid) diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php index 12a988c..7455cb1 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php @@ -30,6 +30,7 @@ use App\ContactsList; use App\Http\Requests\Account\Create\Api\AsAdminRequest; use App\Http\Requests\Account\Update\Api\AsAdminRequest as ApiAsAdminRequest; use App\Services\AccountService; +use App\SipDomain; class AccountController extends Controller { @@ -70,13 +71,13 @@ class AccountController extends Controller $account = Account::findOrFail($accountId); if (!$account->hasTombstone()) { - $tombstone = new AccountTombstone; + $tombstone = new AccountTombstone(); $tombstone->username = $account->username; $tombstone->domain = $account->domain; $tombstone->save(); } - (new AccountService)->destroy($request, $accountId); + (new AccountService())->destroy($request, $accountId); Log::channel('events')->info('API Admin: Account destroyed', ['id' => $account->identifier]); } @@ -138,12 +139,21 @@ class AccountController extends Controller public function store(AsAdminRequest $request) { - return (new AccountService)->store($request)->makeVisible(['confirmation_key', 'provisioning_token']); + // Create the missing SipDomain + if ($request->user()->superAdmin + && $request->has('domain') + && !SipDomain::pluck('domain')->contains($request->get('domain'))) { + $sipDomain = new SipDomain(); + $sipDomain->domain = $request->get('domain'); + $sipDomain->save(); + } + + return (new AccountService())->store($request)->makeVisible(['confirmation_key', 'provisioning_token']); } public function update(ApiAsAdminRequest $request, int $accountId) { - $account = (new AccountService)->update($request, $accountId); + $account = (new AccountService())->update($request, $accountId); Log::channel('events')->info('API Admin: Account updated', ['id' => $account->identifier]); diff --git a/flexiapi/app/Http/Controllers/Api/Admin/DeviceController.php b/flexiapi/app/Http/Controllers/Api/Admin/DeviceController.php index df7033c..9a02eb5 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/DeviceController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/DeviceController.php @@ -19,17 +19,18 @@ namespace App\Http\Controllers\Api\Admin; -use App\Account; use App\Http\Controllers\Controller; use App\Libraries\FlexisipConnector; +use App\Account; +use stdClass; class DeviceController extends Controller { public function index(int $accountId) { - $connector = new FlexisipConnector; + $devices = (new FlexisipConnector)->getDevices(Account::findOrFail($accountId)->identifier); - return $connector->getDevices(Account::findOrFail($accountId)->identifier); + return ($devices->isEmpty()) ? new stdClass : $devices; } public function destroy(int $accountId, string $uuid) diff --git a/flexiapi/app/Http/Controllers/Api/Admin/SipDomainController.php b/flexiapi/app/Http/Controllers/Api/Admin/SipDomainController.php new file mode 100644 index 0000000..eddad07 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Api/Admin/SipDomainController.php @@ -0,0 +1,71 @@ +. +*/ + +namespace App\Http\Controllers\Api\Admin; + +use Illuminate\Http\Request; + +use App\Http\Controllers\Controller; +use App\SipDomain; + +class SipDomainController extends Controller +{ + public function index() + { + return SipDomain::all(); + } + + public function store(Request $request) + { + $request->validate([ + 'domain' => 'required|unique:sip_domains', + 'super' => 'required|boolean', + ]); + + $sipDomain = new SipDomain; + $sipDomain->domain = $request->get('domain'); + $sipDomain->super = $request->has('super') ? (bool)$request->get('super') : false; + $sipDomain->save(); + + return $sipDomain; + } + + public function show(string $domain) + { + return SipDomain::where('domain', $domain)->firstOrFail(); + } + + public function update(Request $request, string $domain) + { + $request->validate([ + 'super' => 'required|boolean', + ]); + + $sipDomain = SipDomain::where('domain', $domain)->firstOrFail(); + $sipDomain->super = $request->has('super') ? (bool)$request->get('super') : false; + $sipDomain->save(); + + return $sipDomain; + } + + public function destroy(string $domain) + { + return SipDomain::where('domain', $domain)->delete(); + } +} diff --git a/flexiapi/app/Http/Controllers/Api/Admin/VcardsStorageController.php b/flexiapi/app/Http/Controllers/Api/Admin/VcardsStorageController.php index 6ffea31..6e06b98 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/VcardsStorageController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/VcardsStorageController.php @@ -1,4 +1,21 @@ . +*/ namespace App\Http\Controllers\Api\Admin; diff --git a/flexiapi/app/Http/Kernel.php b/flexiapi/app/Http/Kernel.php index de54266..cbb88ae 100644 --- a/flexiapi/app/Http/Kernel.php +++ b/flexiapi/app/Http/Kernel.php @@ -71,6 +71,7 @@ class Kernel extends HttpKernel protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class, + 'auth.super_admin' => \App\Http\Middleware\AuthenticateSuperAdmin::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.digest_or_key' => \App\Http\Middleware\AuthenticateDigestOrKey::class, 'auth.jwt' => \App\Http\Middleware\AuthenticateJWT::class, diff --git a/flexiapi/app/Http/Middleware/AuthenticateAdmin.php b/flexiapi/app/Http/Middleware/AuthenticateAdmin.php index e145cbd..83133de 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateAdmin.php +++ b/flexiapi/app/Http/Middleware/AuthenticateAdmin.php @@ -1,4 +1,21 @@ . +*/ namespace App\Http\Middleware; @@ -6,13 +23,6 @@ use Closure; class AuthenticateAdmin { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ public function handle($request, Closure $next) { if (!$request->user()) { diff --git a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php index 83913ee..b11393a 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php +++ b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php @@ -31,13 +31,6 @@ use Validator; class AuthenticateDigestOrKey { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ public function handle(Request $request, Closure $next) { if ($request->bearerToken() && Auth::check()) { diff --git a/flexiapi/app/Http/Middleware/AuthenticateJWT.php b/flexiapi/app/Http/Middleware/AuthenticateJWT.php index d9e4398..df7186c 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateJWT.php +++ b/flexiapi/app/Http/Middleware/AuthenticateJWT.php @@ -1,4 +1,21 @@ . +*/ namespace App\Http\Middleware; diff --git a/flexiapi/app/Http/Middleware/AuthenticateSuperAdmin.php b/flexiapi/app/Http/Middleware/AuthenticateSuperAdmin.php new file mode 100644 index 0000000..159412b --- /dev/null +++ b/flexiapi/app/Http/Middleware/AuthenticateSuperAdmin.php @@ -0,0 +1,34 @@ +. +*/ + +namespace App\Http\Middleware; + +use Closure; + +class AuthenticateSuperAdmin +{ + public function handle($request, Closure $next) + { + if (!$request->user() || !$request->user()->superAdmin) { + return abort(403, 'Unauthorized area'); + } + + return $next($request); + } +} diff --git a/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php b/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php index c036f7d..bc09724 100644 --- a/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php +++ b/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php @@ -7,6 +7,7 @@ use App\Http\Requests\Api as RequestsApi; use App\Http\Requests\AsAdmin; use App\Rules\IsNotPhoneNumber; use App\Rules\PasswordAlgorithm; +use App\SipDomain; class AsAdminRequest extends Request { @@ -29,6 +30,10 @@ class AsAdminRequest extends Request 'nullable', ]; + if ($this->user()->superAdmin) { + $rules['domain'] = ''; + } + if (config('app.allow_phone_number_username_admin_api') == true) { array_splice( $rules['username'], diff --git a/flexiapi/app/Http/Requests/Account/Create/Request.php b/flexiapi/app/Http/Requests/Account/Create/Request.php index 7b09f86..67c8c26 100644 --- a/flexiapi/app/Http/Requests/Account/Create/Request.php +++ b/flexiapi/app/Http/Requests/Account/Create/Request.php @@ -37,6 +37,7 @@ class Request extends FormRequest }), 'filled', ], + 'domain' => 'exists:sip_domains,domain', 'dictionary' => [new Dictionary()], 'password' => 'required|min:3', 'email' => config('app.account_email_unique') diff --git a/flexiapi/app/Http/Requests/Account/Update/Request.php b/flexiapi/app/Http/Requests/Account/Update/Request.php index a821d91..561a1d9 100644 --- a/flexiapi/app/Http/Requests/Account/Update/Request.php +++ b/flexiapi/app/Http/Requests/Account/Update/Request.php @@ -33,6 +33,7 @@ class Request extends FormRequest })->ignore($this->route('account_id'), 'id'), 'filled', ], + 'domain' => 'exists:sip_domains,domain', 'email' => [ 'nullable', 'email', diff --git a/flexiapi/app/Libraries/FlexisipConnector.php b/flexiapi/app/Libraries/FlexisipConnector.php index 6142ce2..7007d43 100644 --- a/flexiapi/app/Libraries/FlexisipConnector.php +++ b/flexiapi/app/Libraries/FlexisipConnector.php @@ -42,8 +42,6 @@ class FlexisipConnector Log::error('Redis server issue: ' . $th->getMessage()); } - if ($devices->isEmpty()) return new stdClass; - return $devices->keyBy('uuid'); } diff --git a/flexiapi/app/SipDomain.php b/flexiapi/app/SipDomain.php new file mode 100644 index 0000000..29235db --- /dev/null +++ b/flexiapi/app/SipDomain.php @@ -0,0 +1,23 @@ + 'boolean', + ]; + + public function accounts() + { + return $this->hasMany(Account::class, 'domain', 'domain'); + } +} diff --git a/flexiapi/composer.phar b/flexiapi/composer.phar index 53f8b81265150bcfe95e433a7310d2e8652e88dc..9ea117b33a2e5d20aee9657bf44f2df490eade08 100755 GIT binary patch delta 17468 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 ?-G;qRcaMk0UU)Xi)C1l+wN3*6y(9IRo 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 diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index 6208f94..187de49 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -15,7 +15,6 @@ return [ 'name' => env('APP_NAME', 'Account Manager'), 'sip_domain' => env('APP_SIP_DOMAIN', 'sip.domain.com'), - 'super_admins_sip_domains' => env('APP_SUPER_ADMINS_SIP_DOMAINS', ''), 'project_url' => env('APP_PROJECT_URL', ''), 'terms_of_use_url' => env('TERMS_OF_USE_URL', ''), diff --git a/flexiapi/database/factories/AccountFactory.php b/flexiapi/database/factories/AccountFactory.php index b105915..577eb00 100644 --- a/flexiapi/database/factories/AccountFactory.php +++ b/flexiapi/database/factories/AccountFactory.php @@ -26,6 +26,7 @@ use Awobaz\Compoships\Database\Eloquent\Factories\ComposhipsFactory; use App\Account; use App\AccountCreationToken; use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; +use App\SipDomain; class AccountFactory extends Factory { @@ -34,10 +35,14 @@ class AccountFactory extends Factory public function definition() { + $domain = SipDomain::count() == 0 + ? SipDomain::factory()->create() + : SipDomain::first(); + return [ 'username' => $this->faker->username, 'display_name' => $this->faker->name, - 'domain' => config('app.sip_domain'), + 'domain' => $domain->domain, 'user_agent' => $this->faker->userAgent, 'confirmation_key' => Str::random(WebAuthenticateController::$emailCodeSize), 'ip_address' => $this->faker->ipv4, @@ -55,6 +60,19 @@ class AccountFactory extends Factory ]); } + public function superAdmin() + { + return $this->state(function (array $attributes) { + $sipDomain = SipDomain::where('domain', $attributes['domain'])->first(); + $sipDomain->super = true; + $sipDomain->save(); + + return [ + 'admin' => true, + ]; + }); + } + public function deactivated() { return $this->state(fn (array $attributes) => [ diff --git a/flexiapi/database/factories/ContactsListFactory.php b/flexiapi/database/factories/ContactsListFactory.php index 0fafdcc..2e32b2b 100644 --- a/flexiapi/database/factories/ContactsListFactory.php +++ b/flexiapi/database/factories/ContactsListFactory.php @@ -1,4 +1,21 @@ . +*/ namespace Database\Factories; diff --git a/flexiapi/database/factories/SipDomainFactory.php b/flexiapi/database/factories/SipDomainFactory.php new file mode 100644 index 0000000..85ab92e --- /dev/null +++ b/flexiapi/database/factories/SipDomainFactory.php @@ -0,0 +1,42 @@ +. +*/ + +namespace Database\Factories; + +use App\SipDomain; +use Illuminate\Database\Eloquent\Factories\Factory; + +class SipDomainFactory extends Factory +{ + protected $model = SipDomain::class; + + public function definition() + { + return [ + 'domain' => config('app.sip_domain'), + ]; + } + + public function secondDomain() + { + return $this->state(fn (array $attributes) => [ + 'domain' => 'second_' . config('app.sip_domain'), + ]); + } +} diff --git a/flexiapi/database/factories/StatisticsCallFactory.php b/flexiapi/database/factories/StatisticsCallFactory.php index de40a2a..f2fbd34 100644 --- a/flexiapi/database/factories/StatisticsCallFactory.php +++ b/flexiapi/database/factories/StatisticsCallFactory.php @@ -1,4 +1,21 @@ . +*/ namespace Database\Factories; diff --git a/flexiapi/database/factories/StatisticsMessageDeviceFactory.php b/flexiapi/database/factories/StatisticsMessageDeviceFactory.php index abb7eaa..5808d2b 100644 --- a/flexiapi/database/factories/StatisticsMessageDeviceFactory.php +++ b/flexiapi/database/factories/StatisticsMessageDeviceFactory.php @@ -1,4 +1,21 @@ . +*/ namespace Database\Factories; diff --git a/flexiapi/database/factories/StatisticsMessageFactory.php b/flexiapi/database/factories/StatisticsMessageFactory.php index 9258165..772e9dd 100644 --- a/flexiapi/database/factories/StatisticsMessageFactory.php +++ b/flexiapi/database/factories/StatisticsMessageFactory.php @@ -1,4 +1,21 @@ . +*/ namespace Database\Factories; diff --git a/flexiapi/database/factories/UserFactory.php b/flexiapi/database/factories/UserFactory.php index 1335d38..a1bf8da 100644 --- a/flexiapi/database/factories/UserFactory.php +++ b/flexiapi/database/factories/UserFactory.php @@ -19,6 +19,7 @@ namespace Database\Factories; +use App\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; diff --git a/flexiapi/database/migrations/2024_06_11_090306_create_sip_domains_table.php b/flexiapi/database/migrations/2024_06_11_090306_create_sip_domains_table.php new file mode 100644 index 0000000..063ab4b --- /dev/null +++ b/flexiapi/database/migrations/2024_06_11_090306_create_sip_domains_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('domain', 64)->unique()->index(); + $table->boolean('super')->default(false); + $table->timestamps(); + }); + + foreach (DB::table('accounts')->select('domain')->distinct()->get()->pluck('domain') as $domain) { + $sipDomain = new SipDomain; + $sipDomain->domain = $domain; + $sipDomain->super = env('APP_ADMINS_MANAGE_MULTI_DOMAINS', false); // historical environnement boolean + $sipDomain->save(); + } + + Schema::table('accounts', function (Blueprint $table) { + $table->foreign('domain')->references('domain') + ->on('sip_domains')->onDelete('cascade'); + }); + } + + public function down() + { + Schema::table('accounts', function (Blueprint $table) { + $table->dropForeign('accounts_domain_foreign'); + }); + Schema::dropIfExists('sip_domains'); + } +}; diff --git a/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php b/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php index 50ff9ec..ae9f892 100644 --- a/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php +++ b/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use App\Account; +use App\SipDomain; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; @@ -28,6 +29,7 @@ class LiblinphoneTesterAccoutSeeder extends Seeder { $accounts = []; $passwords = []; + $domains = []; foreach ($json as $element) { if ($element->type == 'account') { @@ -43,6 +45,8 @@ class LiblinphoneTesterAccoutSeeder extends Seeder ) ); + if(!in_array($element->domain, $domains)) array_push($domains, $element->domain); + if (isset($element->passwords)) { foreach ($element->passwords as $password) { array_push( @@ -76,6 +80,8 @@ class LiblinphoneTesterAccoutSeeder extends Seeder $this->generatePasswordArray($element->idStart + $i, 'secret', 'CLRTXT') ); } + + if(!in_array($element->domain, $domains)) array_push($domains, $element->domain); } } @@ -84,6 +90,13 @@ class LiblinphoneTesterAccoutSeeder extends Seeder Account::withoutGlobalScopes()->whereIn('id', $ids)->delete(); + // Create the domains + foreach ($domains as $domain) { + $sipDomain = SipDomain::where('domain', $domain)->firstOrNew(); + $sipDomain->domain = $domain; + $sipDomain->save(); + } + // And seed the fresh ones DB::table('accounts')->insert($accounts); DB::table('passwords')->insert($passwords); diff --git a/flexiapi/resources/views/account/device/index.blade.php b/flexiapi/resources/views/account/device/index.blade.php index 613402f..a073133 100644 --- a/flexiapi/resources/views/account/device/index.blade.php +++ b/flexiapi/resources/views/account/device/index.blade.php @@ -6,36 +6,35 @@ @section('content') -
-

devices Devices management

-
+
+

devices Devices management

+
- - - - - - - - - @if ($devices->isEmpty()) - - - - @endif - @foreach ($devices as $device) +
User Agent
No Devices
+ - - + + - @endforeach - -
{{ $device->user_agent }} - - Delete - - User Agent
+ + + @if ($devices->isEmpty()) + + No Devices + + @else + @foreach ($devices as $device) + + {{ $device->user_agent }} + + + Delete + + + + @endforeach + @endif + + -@endsection \ No newline at end of file +@endsection diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php index ec8f030..42663d1 100644 --- a/flexiapi/resources/views/admin/account/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/create_edit.blade.php @@ -46,10 +46,13 @@ @include('parts.errors', ['name' => 'username']) -
- user()?->superAdmin) required @else disabled @endif name="domain" - type="text" value="{{ $account->domain ?? config('app.sip_domain') }}" - @if ($account->id) readonly @endif> +
+
diff --git a/flexiapi/resources/views/admin/account/device/index.blade.php b/flexiapi/resources/views/admin/account/device/index.blade.php index ad6bb36..530c174 100644 --- a/flexiapi/resources/views/admin/account/device/index.blade.php +++ b/flexiapi/resources/views/admin/account/device/index.blade.php @@ -27,19 +27,20 @@ No Devices + @else + @foreach ($devices as $device) + + {{ $device->user_agent }} + + + Delete + + + + @endforeach @endif - @foreach ($devices as $device) - - {{ $device->user_agent }} - - - Delete - - - - @endforeach diff --git a/flexiapi/resources/views/admin/contacts_list/delete.blade.php b/flexiapi/resources/views/admin/contacts_list/delete.blade.php index cc910c4..a59e78b 100644 --- a/flexiapi/resources/views/admin/contacts_list/delete.blade.php +++ b/flexiapi/resources/views/admin/contacts_list/delete.blade.php @@ -8,23 +8,21 @@ @endsection @section('content') -

Delete a Contact List

- -
+
+

delete Delete a Contact List

+ Cancel + +
+ @csrf @method('delete')

You are going to permanently delete the following contacts list. Please confirm your action.
- {{ $contacts_list->title }} +

{{ $contacts_list->title }}

-
- Cancel - -
-
@endsection diff --git a/flexiapi/resources/views/admin/sip_domain/create_edit.blade.php b/flexiapi/resources/views/admin/sip_domain/create_edit.blade.php new file mode 100644 index 0000000..308fb4e --- /dev/null +++ b/flexiapi/resources/views/admin/sip_domain/create_edit.blade.php @@ -0,0 +1,50 @@ +@extends('layouts.main') + +@section('breadcrumb') + + +@endsection + +@section('content') +
+ @if ($sip_domain->id) +

dns {{ $sip_domain->domain }}

+ Cancel + + delete + Delete + + + @else +

account_box Create a SIP Domain

+ Cancel + + @endif +
+ +
+ @csrf + @method($sip_domain->id ? 'put' : 'post') + @if (!$sip_domain->id) +
+ + + @include('parts.errors', ['name' => 'domain']) +
+ @endif + +
+ super) checked @endif> +

Enabled

+ super) checked @endif> +

Disabled

+ +
+ +
+@endsection diff --git a/flexiapi/resources/views/admin/sip_domain/delete.blade.php b/flexiapi/resources/views/admin/sip_domain/delete.blade.php new file mode 100644 index 0000000..7812157 --- /dev/null +++ b/flexiapi/resources/views/admin/sip_domain/delete.blade.php @@ -0,0 +1,34 @@ +@extends('layouts.main') + +@section('breadcrumb') + + +@endsection + +@section('content') +
+

delete Delete a SIP Domain

+ Cancel + +
+
+ @csrf + @method('delete') + +
+

You are going to permanently delete the following domain please confirm your action.

+

{{ $sip_domain->domain }}

+

This will also destroy {{ $sip_domain->accounts()->count() }} related accounts

+ + +
+ +
+ + + @include('parts.errors', ['name' => 'domain']) +
+
+@endsection diff --git a/flexiapi/resources/views/admin/sip_domain/index.blade.php b/flexiapi/resources/views/admin/sip_domain/index.blade.php new file mode 100644 index 0000000..b49b4e8 --- /dev/null +++ b/flexiapi/resources/views/admin/sip_domain/index.blade.php @@ -0,0 +1,43 @@ +@extends('layouts.main') + +@section('breadcrumb') + +@endsection + +@section('content') + +
+

dns SIP Domains

+ + add_circle + New SIP Domain + +
+ + + + + + + + + + @foreach ($sip_domains as $sip_domain) + + + + + @endforeach + +
SIP DomainAccounts
+ + {{ $sip_domain->domain }} + @if ($sip_domain->super) Super @endif + + + {{ $sip_domain->accounts_count }} +
+ +@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index 98e590f..5976892 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -24,6 +24,7 @@ The endpoints are accessible using three different models: - Public publicly accessible - User the endpoint can only be accessed by an authenticated user - Admin the endpoint can be only be accessed by an authenticated admin user +- Super Admin the endpoint can be only be accessed by an authenticated super admin user ### Localization @@ -127,6 +128,44 @@ An `account_creation_request_token` is a unique token that can be validated and Create and return an `account_creation_request_token` that should then be validated to be used. +## SIP Domains + +Manage the list of allowed `sip_domains`. The admin accounts declared with a `domain` that is a `super` `sip_domain` will become Super Admin. + +### `GET /sip_domains` +Super Admin + +Get the list of declared SIP Domains. + +### `GET /sip_domains/{domain}` +Super Admin + +Get a SIP Domain. + +### `POST /sip_domains` +Super Admin + +Create a new `sip_domain`. + +JSON parameters: + +* `domain` required, the domain to use, must be unique +* `super` required, boolean, set the domain as a Super Domain + +### `PUT /sip_domains/{domain}` +Super Admin + +Update an existing `sip_domain`. + +JSON parameters: + +* `super` required, boolean, set the domain as a Super Domain + +### `DELETE /sip_domains/{domain}` +Super Admin + +Delete a domain, **be careful, all the related accounts will also be destroyed**. + ## Account Creation Tokens An `account_creation_token` is a unique token that allow the creation or the validation of a unique account. @@ -336,7 +375,7 @@ JSON parameters: * `username` unique username, minimum 6 characters * `password` required minimum 6 characters * `algorithm` required, values can be `SHA-256` or `MD5` -* `domain` **not configurable by default**. Only configurable if the admin is a super admin. Otherwise `APP_SIP_DOMAIN` is used. +* `domain` **not configurable by default**. Only configurable if the admin is a super admin. Otherwise `APP_SIP_DOMAIN` is used. If the domain is not available in the `sip_domains` list, it will be created automatically. * `activated` optional, a boolean, set to `false` by default * `display_name` optional, string * `email` optional, must be an email, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` diff --git a/flexiapi/resources/views/parts/sidebar.blade.php b/flexiapi/resources/views/parts/sidebar.blade.php index efc0620..240f6af 100644 --- a/flexiapi/resources/views/parts/sidebar.blade.php +++ b/flexiapi/resources/views/parts/sidebar.blade.php @@ -8,6 +8,10 @@ $items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'people']; $items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'account_box']; $items['admin.statistics.show'] = ['title' => 'Statistics', 'icon' => 'analytics']; + + if (auth()->user()->superAdmin) { + $items['admin.sip_domains.index'] = ['title' => 'SIP Domains', 'icon' => 'dns']; + } } @endphp diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php index fe9b425..58babb6 100644 --- a/flexiapi/routes/api.php +++ b/flexiapi/routes/api.php @@ -24,6 +24,7 @@ use App\Http\Controllers\Api\Admin\AccountController as AdminAccountController; use App\Http\Controllers\Api\Admin\AccountDictionaryController; use App\Http\Controllers\Api\Admin\AccountTypeController; use App\Http\Controllers\Api\Admin\ContactsListController; +use App\Http\Controllers\Api\Admin\SipDomainController; use App\Http\Controllers\Api\Admin\VcardsStorageController as AdminVcardsStorageController; use App\Http\Controllers\Api\StatisticsMessageController; use App\Http\Controllers\Api\StatisticsCallController; @@ -91,6 +92,17 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo Route::post('messages', 'Api\Admin\MessageController@send'); } + // Super admin + Route::group(['middleware' => ['auth.super_admin']], function () { + Route::prefix('sip_domains')->controller(SipDomainController::class)->group(function () { + Route::get('/', 'index'); + Route::get('{domain}', 'show'); + Route::post('/', 'store'); + Route::put('{domain}', 'update'); + Route::delete('{domain}', 'destroy'); + }); + }); + // Account creation token Route::post('account_creation_tokens', 'Api\Admin\AccountCreationTokenController@create'); diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index ca546f9..aae5ff2 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -38,6 +38,7 @@ use App\Http\Controllers\Admin\AccountController as AdminAccountController; use App\Http\Controllers\Admin\AccountStatisticsController; use App\Http\Controllers\Admin\ContactsListController; use App\Http\Controllers\Admin\ContactsListContactController; +use App\Http\Controllers\Admin\SipDomainController; use App\Http\Controllers\Admin\StatisticsController; use Illuminate\Support\Facades\Route; @@ -152,6 +153,12 @@ Route::group(['middleware' => 'web_panel_enabled'], function () { Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth'); Route::name('admin.')->prefix('admin')->middleware(['auth.admin', 'auth.check_blocked'])->group(function () { + + Route::middleware(['auth.super_admin'])->group(function () { + Route::resource('sip_domains', SipDomainController::class); + Route::get('sip_domains/delete/{id}', 'Admin\SipDomainController@delete')->name('sip_domains.delete'); + }); + Route::name('statistics.')->controller(StatisticsController::class)->prefix('statistics')->group(function () { Route::get('/', 'index')->name('index'); Route::post('call_logs', 'editCallLogs')->name('edit_call_logs'); diff --git a/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php b/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php index 7bd62c2..310e7d4 100644 --- a/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php +++ b/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php @@ -19,6 +19,7 @@ namespace Tests\Feature; +use App\Account; use App\Password; use DateTimeImmutable; use Lcobucci\Clock\FrozenClock; diff --git a/flexiapi/tests/Feature/ApiAccountContactsTest.php b/flexiapi/tests/Feature/ApiAccountContactsTest.php index 542cd56..32ae9a7 100644 --- a/flexiapi/tests/Feature/ApiAccountContactsTest.php +++ b/flexiapi/tests/Feature/ApiAccountContactsTest.php @@ -26,7 +26,7 @@ use App\Password; use Illuminate\Support\Facades\DB; use Tests\TestCase; -class ApiAccountContactTest extends TestCase +class ApiAccountContactsTest extends TestCase { protected $route = '/api/accounts'; protected $contactsListsRoute = '/api/contacts_lists'; diff --git a/flexiapi/tests/Feature/ApiAccountTest.php b/flexiapi/tests/Feature/ApiAccountTest.php index 5b2138f..2caef5e 100644 --- a/flexiapi/tests/Feature/ApiAccountTest.php +++ b/flexiapi/tests/Feature/ApiAccountTest.php @@ -24,6 +24,7 @@ use App\AccountCreationToken; use App\AccountTombstone; use App\ActivationExpiration; use App\Password; +use App\SipDomain; use Carbon\Carbon; use Tests\TestCase; @@ -86,14 +87,13 @@ class ApiAccountTest extends TestCase public function testUsernameNotPhone() { - $password = Password::factory()->admin()->create(); - $password->account->generateApiKey(); - //$password->account->save(); + $account = Account::factory()->admin()->create(); + $account->generateApiKey(); $username = '+33612121212'; - $domain = 'example.com'; + $domain = SipDomain::first()->domain; - $this->keyAuthenticated($password->account) + $this->keyAuthenticated($account) ->json($this->method, $this->route, [ 'username' => $username, 'domain' => $domain, @@ -104,7 +104,7 @@ class ApiAccountTest extends TestCase config()->set('app.allow_phone_number_username_admin_api', true); - $this->keyAuthenticated($password->account) + $this->keyAuthenticated($account) ->json($this->method, $this->route, [ 'username' => $username, 'domain' => $domain, @@ -118,10 +118,9 @@ class ApiAccountTest extends TestCase { $password = Password::factory()->admin()->create(); $password->account->generateApiKey(); - //$password->account->save(); $username = 'blablašŸ”„'; - $domain = 'example.com'; + $domain = SipDomain::first()->domain; $this->keyAuthenticated($password->account) ->json($this->method, $this->route, [ @@ -161,7 +160,7 @@ class ApiAccountTest extends TestCase $password = Password::factory()->admin()->create(); $username = 'foobar'; - $domain = 'example.com'; + $domain = SipDomain::first()->domain; config()->set('app.admins_manage_multi_domains', false); @@ -191,53 +190,48 @@ class ApiAccountTest extends TestCase { $configDomain = 'sip.domain.com'; config()->set('app.sip_domain', $configDomain); - config()->set('app.super_admins_sip_domains', $configDomain); - $password = Password::factory()->admin()->create(); - $password->account->generateApiKey(); - $password->account->save(); + $account = Account::factory()->superAdmin()->create(); + $account->generateApiKey(); + $account->save(); $username = 'foobar'; - $domain1 = 'example.com'; - $domain2 = 'foobar.com'; + $domain1 = SipDomain::first()->domain; + $domain2 = SipDomain::factory()->secondDomain()->create()->domain; - $response0 = $this->keyAuthenticated($password->account) + $this->keyAuthenticated($account) ->json($this->method, $this->route, [ 'username' => $username, 'domain' => $domain1, 'algorithm' => 'SHA-256', 'password' => '123456', - ]); - - $response0 + ]) ->assertStatus(200) ->assertJson([ 'username' => $username, 'domain' => $domain1 ]); - $response1 = $this->keyAuthenticated($password->account) + $this->keyAuthenticated($account) ->json($this->method, $this->route, [ 'username' => $username, 'domain' => $domain2, 'algorithm' => 'SHA-256', 'password' => '123456', - ]); - - $response1 + ]) ->assertStatus(200) ->assertJson([ 'username' => $username, 'domain' => $domain2 ]); - $this->keyAuthenticated($password->account) + $this->keyAuthenticated($account) ->get($this->route) ->assertStatus(200) ->assertJson(['data' => [ [ - 'username' => $password->account->username, - 'domain' => $password->account->domain + 'username' => $account->username, + 'domain' => $account->domain ], [ 'username' => $username, @@ -250,12 +244,68 @@ class ApiAccountTest extends TestCase ]]); } + public function testCreateDomainAsAdmin() + { + $admin = Account::factory()->admin()->create(); + $admin->generateApiKey(); + $admin->save(); + + $username = 'foo'; + $newDomain = 'new.domain'; + + // Standard admin + $this->keyAuthenticated($admin) + ->json($this->method, $this->route, [ + 'username' => $username, + 'domain' => $newDomain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertStatus(422); + + $this->keyAuthenticated($admin) + ->json($this->method, $this->route, [ + 'username' => $username, + 'domain' => $admin->domain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertStatus(200); + } + + public function testCreateDomainAsSuperAdmin() + { + $superAdmin = Account::factory()->superAdmin()->create(); + $superAdmin->generateApiKey(); + $superAdmin->save(); + + $username = 'foo'; + $newDomain = 'new.domain'; + + // Super admin + $this->keyAuthenticated($superAdmin) + ->json($this->method, $this->route, [ + 'username' => $username, + 'domain' => $newDomain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertStatus(200) + ->assertJson([ + 'username' => $username, + 'domain' => $newDomain + ]); + + $this->assertDatabaseHas('sip_domains', [ + 'domain' => $newDomain + ]); + } + + public function testDomainInTestDeployment() { $configDomain = 'testdomain.com'; - $adminDomain = 'admindomain.com'; - config()->set('app.super_admins_sip_domains', $adminDomain); - config()->set('app.sip_domain', $adminDomain); + config()->set('app.sip_domain', $configDomain); $password = Password::factory()->admin()->create(); $username = 'foobar'; @@ -358,11 +408,12 @@ class ApiAccountTest extends TestCase $entryValue = 'bar'; $entryNewKey = 'new_key'; $entryNewValue = 'new_value'; + $domain = SipDomain::first()->domain; $result = $this->keyAuthenticated($admin) ->json($this->method, $this->route, [ 'username' => 'john', - 'domain' => 'lennon.com', + 'domain' => $domain, 'password' => 'password123', 'algorithm' => 'SHA-256', 'dictionary' => [ @@ -381,7 +432,7 @@ class ApiAccountTest extends TestCase $this->keyAuthenticated($admin) ->json($this->method, $this->route, [ 'username' => 'john2', - 'domain' => 'lennon.com', + 'domain' => $domain, 'password' => 'password123', 'algorithm' => 'SHA-256', 'dictionary' => [ @@ -392,7 +443,7 @@ class ApiAccountTest extends TestCase $this->keyAuthenticated($admin) ->json($this->method, $this->route, [ 'username' => 'john2', - 'domain' => 'lennon.com', + 'domain' => $domain, 'password' => 'password123', 'algorithm' => 'SHA-256', 'dictionary' => 'hop' @@ -479,7 +530,6 @@ class ApiAccountTest extends TestCase public function testActivated() { $password = Password::factory()->admin()->create(); - $username = 'username'; $response0 = $this->generateFirstResponse($password); @@ -672,13 +722,14 @@ class ApiAccountTest extends TestCase $password->account->generateApiKey(); $username = 'username'; + $domain = SipDomain::first()->domain; $response = $this->generateFirstResponse($password, $this->method, $this->route); $this->generateSecondResponse($password, $response) ->json($this->method, $this->route, [ 'username' => $username, 'email' => 'email@test.com', - 'domain' => 'server.com', + 'domain' => $domain, 'algorithm' => 'SHA-256', 'password' => 'nonascii€', ]) diff --git a/flexiapi/tests/Feature/ApiAccountVcardsStorageTest.php b/flexiapi/tests/Feature/ApiAccountVcardsStorageTest.php index 7071c00..f77a486 100644 --- a/flexiapi/tests/Feature/ApiAccountVcardsStorageTest.php +++ b/flexiapi/tests/Feature/ApiAccountVcardsStorageTest.php @@ -22,7 +22,7 @@ namespace Tests\Feature; use App\Account; use Tests\TestCase; -class ApiVcardsStorageTest extends TestCase +class ApiAccountVcardsStorageTest extends TestCase { protected $route = '/api/accounts/me/vcards-storage'; protected $method = 'POST'; diff --git a/flexiapi/tests/Feature/ApiSipDomainTest.php b/flexiapi/tests/Feature/ApiSipDomainTest.php new file mode 100644 index 0000000..d389ed7 --- /dev/null +++ b/flexiapi/tests/Feature/ApiSipDomainTest.php @@ -0,0 +1,117 @@ +. +*/ + +namespace Tests\Feature; + +use App\Account; +use App\SipDomain; +use Tests\TestCase; + +class ApiSipDomainTest extends TestCase +{ + protected $route = '/api/sip_domains'; + + public function testBaseAdmin() + { + $admin = Account::factory()->admin()->create(); + $admin->generateApiKey(); + + $secondDomain = SipDomain::factory()->secondDomain()->create(); + $username = 'foo'; + + // Admin domain + $this->keyAuthenticated($admin) + ->json('POST', '/api/accounts', [ + 'username' => $username, + 'domain' => $admin->domain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertStatus(200); + + // Second domain + $this->keyAuthenticated($admin) + ->json('POST', '/api/accounts', [ + 'username' => $username, + // The domain is ignored there, to fallback on the admin one + 'domain' => $secondDomain->domain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertJsonValidationErrors(['username']); + + // Admin domain is now a super domain + SipDomain::where('domain', $admin->domain)->update(['super' => true]); + + $this->keyAuthenticated($admin) + ->json('POST', '/api/accounts', [ + 'username' => $username, + 'domain' => $secondDomain->domain, + 'algorithm' => 'SHA-256', + 'password' => '123456', + ]) + ->assertStatus(200); + } + + public function testSuperAdmin() + { + $admin = Account::factory()->superAdmin()->create(); + $admin->generateApiKey(); + + $thirdDomain = 'third.domain'; + + $this->keyAuthenticated($admin) + -> json('POST', $this->route, [ + 'domain' => $thirdDomain, + 'super' => false + ]) + ->assertStatus(201); + + $this->keyAuthenticated($admin) + ->json('GET', $this->route) + ->assertJsonFragment([ + 'domain' => $thirdDomain, + 'super' => false + ]) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('PUT', $this->route . '/' . $thirdDomain, [ + 'super' => true + ]) + ->assertJsonFragment([ + 'domain' => $thirdDomain, + 'super' => true + ]) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('DELETE', $this->route . '/' . $thirdDomain) + ->assertStatus(200); + + // Only the admin domain remains + $this->keyAuthenticated($admin) + ->json('GET', $this->route) + ->assertJsonFragment([ + 'domain' => $admin->domain, + 'super' => true + ]) + ->assertStatus(200); + } +}