Fix FLEXIAPI-182 Replace APP_SUPER_ADMINS_SIP_DOMAINS with a proper sip_domains table, API endpoints, UI panels, tests and documentation

This commit is contained in:
Timothée Jaussoin 2024-07-01 14:11:47 +00:00
parent e913b4a584
commit 2ed4f02c11
57 changed files with 1033 additions and 148 deletions

View file

@ -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/<id> 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

View file

@ -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.

View file

@ -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=

View file

@ -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;
}
/**

View file

@ -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?}';

View file

@ -20,8 +20,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
use App\DigestNonce;
class ClearNonces extends Command

View file

@ -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
{

View file

@ -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)

View file

@ -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,

View file

@ -0,0 +1,50 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -20,8 +20,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
use App\Account;
class RemoveUnconfirmedAccounts extends Command

View file

@ -24,6 +24,7 @@ use Carbon\Carbon;
class Device extends Model
{
protected $fillable = ['user_agent'];
public function fromRedisContact(string $contact)
{
preg_match("/<(.*)>;(.*)/", $contact, $matches);

View file

@ -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')

View file

@ -0,0 +1,94 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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');
}
}

View file

@ -43,6 +43,7 @@ use App\Rules\WithoutSpaces;
use App\Rules\PasswordAlgorithm;
use App\Services\AccountService;
use App\SipDomain;
class AccountController extends Controller
{

View file

@ -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)

View file

@ -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]);

View file

@ -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)

View file

@ -0,0 +1,71 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin;

View file

@ -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,

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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()) {

View file

@ -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()) {

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Middleware;

View file

@ -0,0 +1,34 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View file

@ -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'],

View file

@ -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')

View file

@ -33,6 +33,7 @@ class Request extends FormRequest
})->ignore($this->route('account_id'), 'id'),
'filled',
],
'domain' => 'exists:sip_domains,domain',
'email' => [
'nullable',
'email',

View file

@ -42,8 +42,6 @@ class FlexisipConnector
Log::error('Redis server issue: ' . $th->getMessage());
}
if ($devices->isEmpty()) return new stdClass;
return $devices->keyBy('uuid');
}

View file

@ -0,0 +1,23 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class SipDomain extends Model
{
use HasFactory;
protected $hidden = ['id'];
protected $casts = [
'super' => 'boolean',
];
public function accounts()
{
return $this->hasMany(Account::class, 'domain', 'domain');
}
}

Binary file not shown.

View file

@ -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', ''),

View file

@ -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) => [

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Database\Factories;

View file

@ -0,0 +1,42 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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'),
]);
}
}

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Database\Factories;

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Database\Factories;

View file

@ -1,4 +1,21 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Database\Factories;

View file

@ -19,6 +19,7 @@
namespace Database\Factories;
use App\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

View file

@ -0,0 +1,40 @@
<?php
use App\SipDomain;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('sip_domains', function (Blueprint $table) {
$table->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');
}
};

View file

@ -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);

View file

@ -6,36 +6,35 @@
@section('content')
<header>
<h1><i class="material-symbols-outlined">devices</i> Devices management</h1>
</header>
<header>
<h1><i class="material-symbols-outlined">devices</i> Devices management</h1>
</header>
<table>
<thead>
<tr>
<th>User Agent</th>
<th></th>
</tr>
</thead>
<tbody>
@if ($devices->isEmpty())
<tr class="empty">
<td colspan="3">No Devices</td>
</tr>
@endif
@foreach ($devices as $device)
<table>
<thead>
<tr>
<td>{{ $device->user_agent }}</td>
<td>
<a type="button"
class="btn"
href="{{ route('account.device.delete', [$device->uuid]) }}">
Delete
</a>
</td>
<th>User Agent</th>
<th></th>
</tr>
@endforeach
</tbody>
</table>
</thead>
<tbody>
@if ($devices->isEmpty())
<tr class="empty">
<td colspan="3">No Devices</td>
</tr>
@else
@foreach ($devices as $device)
<tr>
<td>{{ $device->user_agent }}</td>
<td>
<a type="button" class="btn" href="{{ route('account.device.delete', [$device->uuid]) }}">
Delete
</a>
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
@endsection
@endsection

View file

@ -46,10 +46,13 @@
<label for="username">Username</label>
@include('parts.errors', ['name' => 'username'])
</div>
<div>
<input placeholder="domain.com" @if (auth()->user()?->superAdmin) required @else disabled @endif name="domain"
type="text" value="{{ $account->domain ?? config('app.sip_domain') }}"
@if ($account->id) readonly @endif>
<div class="select">
<select name="domain" @if (auth()->user()?->superAdmin) required @else disabled @endif>
@foreach ($domains as $sipDomain)
<option value="{{ $sipDomain->domain }}" @if ($account->domain == $sipDomain->domain) selected="selected" @endif>
{{ $sipDomain->domain }}</option>
@endforeach
</select>
<label for="domain">Domain</label>
</div>

View file

@ -27,19 +27,20 @@
<tr class="empty">
<td colspan="3">No Devices</td>
</tr>
@else
@foreach ($devices as $device)
<tr>
<td>{{ $device->user_agent }}</td>
<td>
<a type="button"
class="btn"
href="{{ route('admin.account.device.delete', [$account, $device->uuid]) }}">
Delete
</a>
</td>
</tr>
@endforeach
@endif
@foreach ($devices as $device)
<tr>
<td>{{ $device->user_agent }}</td>
<td>
<a type="button"
class="btn"
href="{{ route('admin.account.device.delete', [$account, $device->uuid]) }}">
Delete
</a>
</td>
</tr>
@endforeach
</tbody>
</table>

View file

@ -8,23 +8,21 @@
@endsection
@section('content')
<h2>Delete a Contact List</h2>
<form method="POST" action="{{ route('admin.contacts_lists.destroy', $contacts_list->id) }}" accept-charset="UTF-8">
<header>
<h2><i class="material-symbols-outlined">delete</i> Delete a Contact List</h2>
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}" class="btn btn-secondary oppose">Cancel</a>
<input form="delete" class="btn" type="submit" value="Delete">
</header>
<form id="delete" method="POST" action="{{ route('admin.contacts_lists.destroy', $contacts_list->id) }}" accept-charset="UTF-8">
@csrf
@method('delete')
<div class="large">
<p>You are going to permanently delete the following contacts list. Please confirm your action.<br />
<b>{{ $contacts_list->title }}</b>
<h3>{{ $contacts_list->title }}</h3>
</p>
<input name="contacts_lists_id" type="hidden" value="{{ $contacts_list->id }}">
</div>
<div>
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}" class="btn btn-secondary">Cancel</a>
<input class="btn" type="submit" value="Delete">
</div>
</form>
@endsection

View file

@ -0,0 +1,50 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item">
<a href="{{ route('admin.sip_domains.index') }}">SIP Domains</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Edit</li>
@endsection
@section('content')
<header>
@if ($sip_domain->id)
<h1><i class="material-symbols-outlined">dns</i> {{ $sip_domain->domain }}</h1>
<a href="{{ route('admin.sip_domains.index') }}" class="btn btn-secondary oppose">Cancel</a>
<a class="btn btn-secondary" href="{{ route('admin.sip_domains.delete', $sip_domain->id) }}">
<i class="material-symbols-outlined">delete</i>
Delete
</a>
<input form="create_edit_sip_domains" class="btn" type="submit" value="Update">
@else
<h1><i class="material-symbols-outlined">account_box</i> Create a SIP Domain</h1>
<a href="{{ route('admin.sip_domains.index') }}" class="btn btn-secondary oppose">Cancel</a>
<input form="create_edit_sip_domains" class="btn" type="submit" value="Create">
@endif
</header>
<form method="POST" id="create_edit_sip_domains"
action="{{ $sip_domain->id ? route('admin.sip_domains.update', $sip_domain->id) : route('admin.sip_domains.store') }}"
accept-charset="UTF-8">
@csrf
@method($sip_domain->id ? 'put' : 'post')
@if (!$sip_domain->id)
<div>
<input placeholder="Name" required="required" name="domain" type="text"
value="{{ $sip_domain->domain ?? old('domain') }}">
<label for="username">Domain</label>
@include('parts.errors', ['name' => 'domain'])
</div>
@endif
<div>
<input name="super" value="true" type="radio" @if ($sip_domain->super) checked @endif>
<p>Enabled</p>
<input name="super" value="false" type="radio" @if (!$sip_domain->super) checked @endif>
<p>Disabled</p>
<label>Super domain</label>
</div>
</form>
@endsection

View file

@ -0,0 +1,34 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item">
<a href="{{ route('admin.sip_domains.index') }}">SIP Domains</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
@endsection
@section('content')
<header>
<h1><i class="material-symbols-outlined">delete</i> Delete a SIP Domain</h1>
<a href="{{ route('admin.sip_domains.edit', $sip_domain->id) }}" class="btn btn-secondary oppose">Cancel</a>
<input form="delete" class="btn" type="submit" value="Delete">
</header>
<form id="delete" method="POST" action="{{ route('admin.sip_domains.destroy', $sip_domain->id) }}" accept-charset="UTF-8">
@csrf
@method('delete')
<div class="large">
<p>You are going to permanently delete the following domain please confirm your action.</p>
<h3>{{ $sip_domain->domain }}</h3>
<p>This will also destroy <b>{{ $sip_domain->accounts()->count() }} related accounts</b></p>
<input name="sip_domain" type="hidden" value="{{ $sip_domain->id }}">
</div>
<div>
<input placeholder="domain.tld" required="required" name="domain" type="text">
<label for="username">Please retype the domain here to confirm</label>
@include('parts.errors', ['name' => 'domain'])
</div>
</form>
@endsection

View file

@ -0,0 +1,43 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
SIP Domains
</li>
@endsection
@section('content')
<header>
<h1><i class="material-symbols-outlined">dns</i> SIP Domains</h1>
<a class="btn oppose" href="{{ route('admin.sip_domains.create') }}">
<i class="material-symbols-outlined">add_circle</i>
New SIP Domain
</a>
</header>
<table>
<thead>
<tr>
<th>SIP Domain</th>
<th>Accounts</th>
</tr>
</thead>
<tbody>
@foreach ($sip_domains as $sip_domain)
<tr>
<td>
<a href="{{ route('admin.sip_domains.edit', $sip_domain->id) }}">
{{ $sip_domain->domain }}
@if ($sip_domain->super) <span class="badge badge-error" title="Super domain">Super</span> @endif
</a>
</td>
<td>
{{ $sip_domain->accounts_count }}
</td>
</tr>
@endforeach
</tbody>
</table>
@endsection

View file

@ -24,6 +24,7 @@ The endpoints are accessible using three different models:
- <span class="badge badge-success">Public</span> publicly accessible
- <span class="badge badge-info">User</span> the endpoint can only be accessed by an authenticated user
- <span class="badge badge-warning">Admin</span> the endpoint can be only be accessed by an authenticated admin user
- <span class="badge badge-error">Super Admin</span> 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 <span class="badge badge-error">Super Admin</span>.
### `GET /sip_domains`
<span class="badge badge-error">Super Admin</span>
Get the list of declared SIP Domains.
### `GET /sip_domains/{domain}`
<span class="badge badge-error">Super Admin</span>
Get a SIP Domain.
### `POST /sip_domains`
<span class="badge badge-error">Super Admin</span>
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}`
<span class="badge badge-error">Super Admin</span>
Update an existing `sip_domain`.
JSON parameters:
* `super` required, boolean, set the domain as a Super Domain
### `DELETE /sip_domains/{domain}`
<span class="badge badge-error">Super Admin</span>
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`

View file

@ -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

View file

@ -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');

View file

@ -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');

View file

@ -19,6 +19,7 @@
namespace Tests\Feature;
use App\Account;
use App\Password;
use DateTimeImmutable;
use Lcobucci\Clock\FrozenClock;

View file

@ -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';

View file

@ -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€',
])

View file

@ -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';

View file

@ -0,0 +1,117 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}