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 53f8b81..9ea117b 100755 Binary files a/flexiapi/composer.phar and b/flexiapi/composer.phar differ 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); + } +}