mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
Fix FLEXIAPI-220 Migrate SIP Domains to Spaces
This commit is contained in:
parent
0d6bc37207
commit
93c98ae73f
84 changed files with 1470 additions and 1299 deletions
|
|
@ -14,14 +14,6 @@ rocky9-deploy:
|
|||
- rocky9-package
|
||||
- rocky9-test
|
||||
|
||||
debian11-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
- ./deploy_packages.sh debian bullseye
|
||||
needs:
|
||||
- debian11-package
|
||||
- debian11-test
|
||||
|
||||
debian12-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ rocky9-package:
|
|||
- dnf -y install php-sodium
|
||||
- make rpm-el9
|
||||
|
||||
debian11-package:
|
||||
extends: .debian_package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
|
||||
|
||||
debian12-package:
|
||||
extends: .debian_package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ rocky9-test:
|
|||
- php artisan key:generate
|
||||
- vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log
|
||||
|
||||
debian11-test:
|
||||
extends: .debian-test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
|
||||
needs:
|
||||
- debian11-package
|
||||
|
||||
debian12-test:
|
||||
extends: .debian-test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
variables:
|
||||
ROCKY_8_IMAGE_VERSION: 20241113_143521_update_php_82
|
||||
ROCKY_9_IMAGE_VERSION: 20241114_161138_remove_redis
|
||||
DEBIAN_11_IMAGE_VERSION: 20241112_113527_update_package_and_dependencies
|
||||
DEBIAN_12_IMAGE_VERSION: 20241112_113948_update_package_and_dependencies
|
||||
DEBIAN_12_IMAGE_VERSION: 20241112_113948_update_package_and_dependencies
|
||||
PHP_REDIS_REMI_VERSION: php-pecl-redis6-6.1.0-1
|
||||
PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.16-2
|
||||
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-3
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
v1.7
|
||||
----
|
||||
- Fix FLEXIAPI-206 Upgrade to Laravel 10, PHP 8.1 minimum and bump all the related dependencies
|
||||
- Fix FLEXIAPI-206 Upgrade to Laravel 10, PHP 8.1 minimum and bump all the related dependencies, drop Debian 11 Bullseye
|
||||
- Fix FLEXIAPI-220 Migrate SIP Domains to Spaces
|
||||
|
||||
v1.6
|
||||
----
|
||||
|
|
@ -29,7 +30,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-182 Replace APP_SUPER_ADMINS_SIP_DOMAINS with a proper spaces 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
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -57,7 +57,7 @@ package-end-common:
|
|||
rm -rf $(OUTPUT_DIR)/rpmbuild/SPECS $(OUTPUT_DIR)/rpmbuild/SOURCES $(OUTPUT_DIR)/rpmbuild/SRPMS $(OUTPUT_DIR)/rpmbuild/BUILD $(OUTPUT_DIR)/rpmbuild/BUILDROOT
|
||||
|
||||
rpm-el8-only:
|
||||
sed -i 's/Requires:.*/Requires: php >= 8.0, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring/g' $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
|
||||
sed -i 's/Requires:.*/Requires: php >= 8.1, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring/g' $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
|
||||
rpmbuild -v -bb --define 'dist .el8' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
|
||||
@echo "📦✅ RPM el8 Package Created"
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ deb-only:
|
|||
fakeroot alien -g -k --scripts $(OUTPUT_DIR)/rpmbuild/tmp.rpm
|
||||
rm -r $(OUTPUT_DIR)/rpmbuild
|
||||
rm -rf $(OUTPUT_DIR)/*.orig
|
||||
sed -i 's/Depends:.*/Depends: $${shlibs:Depends}, php (>= 8.0), php-xml, php-pdo, php-gd, php-redis, php-mysql, php-mbstring, php-sqlite3/g' $(OUTPUT_DIR)/bc-flexisip-account-manager*/debian/control
|
||||
sed -i 's/Depends:.*/Depends: $${shlibs:Depends}, php (>= 8.1), php-xml, php-pdo, php-gd, php-redis, php-mysql, php-mbstring, php-sqlite3/g' $(OUTPUT_DIR)/bc-flexisip-account-manager*/debian/control
|
||||
|
||||
cd `ls -rt $(OUTPUT_DIR) | tail -1` && dpkg-buildpackage --no-sign
|
||||
@echo "📦✅ DEB Package Created"
|
||||
|
|
|
|||
|
|
@ -148,11 +148,11 @@ 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 Space
|
||||
|
||||
Create or update a SIP Domain, required to then create accounts afterward. The `super` option enable/disable the domain as a super domain.
|
||||
Create or update a Space, 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}
|
||||
php artisan spaces:create-update {domain} {--super}
|
||||
|
||||
### Create an admin account
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|||
- **New DotEnv variable:** `JWT_RSA_PUBLIC_KEY_PEM=`
|
||||
- **New DotEnv variable:** `JWT_SIP_IDENTIFIER=sip_identifier`
|
||||
- **Super-domains and super-admins support:** Introduce SIP domains management. The app accounts are now divided by their domains with their own respective administrators that can only see and manage their own domain accounts and settings. On top of that it is possible to configure a SIP domain as a "super-domain" and then allow its admins to become "super-admins". Those super-admins will then be able to manage all the accounts handled by the instance and create/edit/delete the other SIP domains. Add new endpoints and a new super-admin role in the API to manage the SIP domains. SIP domains can also be created and updated directly from the console using a new artisan script (documented in the README);
|
||||
- **New Artisan script:** `php artisan sip_domains:create-update {domain} {--super}`
|
||||
- **New Artisan script:** `php artisan spaces:create-update {domain} {--super}`
|
||||
- **Account Dictionary:** Each account can now handle a specific dictionary, configurable by the API or directly the web panel. This dictionary allows developers to store arbitrary `key -> value pairs` on each accounts.
|
||||
- **Vcard storage:** Attach custom vCards on a dedicated account using new endpoints in the API. The published vCard are validated before being stored.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ APP_NAME=FlexiAPI
|
|||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
APP_SIP_DOMAIN=sip.example.com
|
||||
APP_ROOT_DOMAIN=
|
||||
|
||||
APP_LINPHONE_DAEMON_UNIX_PATH=
|
||||
APP_FLEXISIP_PUSHER_PATH=
|
||||
|
|
|
|||
|
|
@ -178,9 +178,9 @@ class Account extends Authenticatable
|
|||
return $this->belongsToMany(AccountType::class);
|
||||
}
|
||||
|
||||
public function sipDomain()
|
||||
public function space()
|
||||
{
|
||||
return $this->hasOne(SipDomain::class, 'domain', 'domain');
|
||||
return $this->hasOne(Space::class, 'domain', 'domain');
|
||||
}
|
||||
|
||||
public function statisticsFromCalls()
|
||||
|
|
@ -336,7 +336,7 @@ class Account extends Authenticatable
|
|||
|
||||
public function getSuperAdminAttribute(): bool
|
||||
{
|
||||
return SipDomain::where('domain', $this->domain)->where('super', true)->exists() && $this->admin;
|
||||
return Space::where('domain', $this->domain)->where('super', true)->exists() && $this->admin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use Illuminate\Console\Command;
|
|||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
|
||||
class CreateAdminAccount extends Command
|
||||
{
|
||||
|
|
@ -37,7 +37,7 @@ class CreateAdminAccount extends Command
|
|||
|
||||
public function handle()
|
||||
{
|
||||
$sipDomains = SipDomain::all('domain')->pluck('domain');
|
||||
$spaces = Space::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');
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ class CreateAdminAccount extends Command
|
|||
}
|
||||
|
||||
if (!$this->option('domain')) {
|
||||
$domain = $this->ask('What will be the admin domain? Default: ' . $sipDomains->first());
|
||||
$domain = $this->ask('What will be the admin domain? Default: ' . $spaces->first());
|
||||
}
|
||||
|
||||
if (!$this->option('password')) {
|
||||
|
|
@ -58,11 +58,11 @@ class CreateAdminAccount extends Command
|
|||
}
|
||||
|
||||
$username = $username ?? 'admin';
|
||||
$domain = $domain ?? $sipDomains->first();
|
||||
$domain = $domain ?? $spaces->first();
|
||||
$password = $password ?? 'change_me';
|
||||
|
||||
if (!$sipDomains->contains($domain)) {
|
||||
$this->error('The domain must be one of the following ones: ' . $sipDomains->implode(', '));
|
||||
if (!$spaces->contains($domain)) {
|
||||
$this->error('The domain must be one of the following ones: ' . $spaces->implode(', '));
|
||||
$this->comment('You can create an extra domain using the dedicated console command');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class CreateAdminTest extends Command
|
|||
$username = 'admin_test';
|
||||
$domain = 'sip.example.org';
|
||||
|
||||
$this->call('sip_domains:create-update', [
|
||||
$this->call('spaces:create-update', [
|
||||
'domain' => $domain,
|
||||
'--super' => 'true'
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -17,33 +17,34 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands\SipDomains;
|
||||
namespace App\Console\Commands\Spaces;
|
||||
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CreateUpdate extends Command
|
||||
{
|
||||
protected $signature = 'sip_domains:create-update {domain} {--super}';
|
||||
protected $description = 'Create a SIP Domain';
|
||||
protected $signature = 'spaces:create-update {sip_domain} {host} {--super}';
|
||||
protected $description = 'Create a Space';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Your will create or update a SIP Domain in the database');
|
||||
$this->info('Your will create or update a Space in the database');
|
||||
|
||||
$sipDomain = SipDomain::where('domain', $this->argument('domain'))->firstOrNew();
|
||||
$sipDomain->domain = $this->argument('domain');
|
||||
$space = Space::where('domain', $this->argument('sip_domain'))->firstOrNew();
|
||||
$space->host = $this->argument('host');
|
||||
$space->domain = $this->argument('sip_domain');
|
||||
|
||||
$sipDomain->exists
|
||||
$space->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
|
||||
$space->super = (bool)$this->option('super');
|
||||
$space->super
|
||||
? $this->info('Set as a super domain')
|
||||
: $this->info('Set as a normal domain');
|
||||
|
||||
$sipDomain->save();
|
||||
$space->save();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ function generateNonce(): string
|
|||
|
||||
function getRequestBoolean(Request $request, string $key): bool
|
||||
{
|
||||
return $request->has($key) ? $request->get($key) == "true" : false;
|
||||
return $request->has($key) ? $request->get($key) == "on" : false;
|
||||
}
|
||||
|
||||
function generateValidNonce(Account $account): string
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class AuthTokenController extends Controller
|
|||
|
||||
$authToken->delete();
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,6 +87,6 @@ class AuthTokenController extends Controller
|
|||
$authToken->save();
|
||||
}
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,13 @@ class AuthenticateController extends Controller
|
|||
|
||||
public function login(Request $request)
|
||||
{
|
||||
if (Auth::user()) {
|
||||
if ($request->user()) {
|
||||
if ($request->user()->superAdmin) {
|
||||
return redirect()->route('admin.spaces.index');
|
||||
} elseif ($request->user()->admin) {
|
||||
return redirect()->route('admin.spaces.me');
|
||||
}
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +73,7 @@ class AuthenticateController extends Controller
|
|||
bchash($account->username, $account->resolvedRealm, $request->get('password'), $password->algorithm)
|
||||
)) {
|
||||
Auth::login($account);
|
||||
return redirect()->route('account.dashboard');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +100,7 @@ class AuthenticateController extends Controller
|
|||
|
||||
Auth::login($account);
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
|
||||
public function loginAuthToken(Request $request, ?string $token = null)
|
||||
|
|
@ -120,7 +126,7 @@ class AuthenticateController extends Controller
|
|||
|
||||
$authToken->delete();
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
|
||||
return view('account.authenticate.auth_token', [
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class ProvisioningController extends Controller
|
|||
if ($account) {
|
||||
$ui = $xpath->query("//section[@name='ui']")->item(0);
|
||||
|
||||
if ($ui == null && $account->sipDomain) {
|
||||
if ($ui == null && $account->space) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'ui');
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ class ProvisioningController extends Controller
|
|||
'max_account',
|
||||
] as $key) {
|
||||
// Cast the booleans into integers
|
||||
$entry = $dom->createElement('entry', (int)$account->sipDomain->$key);
|
||||
$entry = $dom->createElement('entry', (int)$account->space->$key);
|
||||
$entry->setAttribute('name', $key);
|
||||
$section->appendChild($entry);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +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;
|
||||
use App\Space;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
|
@ -61,6 +61,9 @@ class AccountController extends Controller
|
|||
}
|
||||
|
||||
return view('admin.account.index', [
|
||||
'space' => (!$request->user()->superAdmin)
|
||||
? $request->user()->space
|
||||
: null,
|
||||
'domains' => Account::groupBy('domain')->pluck('domain'),
|
||||
'contacts_lists' => ContactsList::all()->pluck('title', 'id'),
|
||||
'accounts' => $accounts->paginate(20)->appends($request->query()),
|
||||
|
|
@ -74,11 +77,21 @@ class AccountController extends Controller
|
|||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$account = new Account;
|
||||
|
||||
if ($request->has('admin')) {
|
||||
$account->admin = true;
|
||||
}
|
||||
|
||||
if ($request->has('domain')) {
|
||||
$account->domain = $request->get('domain');
|
||||
}
|
||||
|
||||
return view('admin.account.create_edit', [
|
||||
'account' => new Account,
|
||||
'account' => $account,
|
||||
'domains' => $request->user()?->superAdmin
|
||||
? SipDomain::all()
|
||||
: SipDomain::where('domain', $request->user()->domain)->get(),
|
||||
? Space::notFull()->get()
|
||||
: Space::where('domain', $request->user()->domain)->get(),
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols
|
||||
]);
|
||||
}
|
||||
|
|
@ -100,8 +113,8 @@ class AccountController extends Controller
|
|||
'account' => $account,
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols,
|
||||
'domains' => $request->user()?->superAdmin
|
||||
? SipDomain::all()
|
||||
: SipDomain::where('domain', $account->domain)->get(),
|
||||
? Space::all()
|
||||
: Space::where('domain', $account->domain)->get(),
|
||||
'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($accountId) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
<?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 = $this->setConfig($request, $sipDomain);
|
||||
$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)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$sipDomain = SipDomain::findOrFail($id);
|
||||
$sipDomain = $this->setConfig($request, $sipDomain);
|
||||
$sipDomain->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
private function setConfig(Request $request, SipDomain $sipDomain)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$sipDomain->super = getRequestBoolean($request, 'super');
|
||||
$sipDomain->disable_chat_feature = getRequestBoolean($request, 'disable_chat_feature');
|
||||
$sipDomain->disable_meetings_feature = getRequestBoolean($request, 'disable_meetings_feature');
|
||||
$sipDomain->disable_broadcast_feature = getRequestBoolean($request, 'disable_broadcast_feature');
|
||||
$sipDomain->hide_settings = getRequestBoolean($request, 'hide_settings');
|
||||
$sipDomain->max_account = $request->get('max_account', 0);
|
||||
$sipDomain->hide_account_settings = getRequestBoolean($request, 'hide_account_settings');
|
||||
$sipDomain->disable_call_recordings_feature = getRequestBoolean($request, 'disable_call_recordings_feature');
|
||||
$sipDomain->only_display_sip_uri_username = getRequestBoolean($request, 'only_display_sip_uri_username');
|
||||
$sipDomain->assistant_hide_create_account = getRequestBoolean($request, 'assistant_hide_create_account');
|
||||
$sipDomain->assistant_disable_qr_code = getRequestBoolean($request, 'assistant_disable_qr_code');
|
||||
$sipDomain->assistant_hide_third_party_account = getRequestBoolean($request, 'assistant_hide_third_party_account');
|
||||
|
||||
return $sipDomain;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
164
flexiapi/app/Http/Controllers/Admin/SpaceController.php
Normal file
164
flexiapi/app/Http/Controllers/Admin/SpaceController.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?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\Space;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SpaceController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.space.index', ['spaces' => Space::withCount('accounts')->get()]);
|
||||
}
|
||||
|
||||
public function me(Request $request)
|
||||
{
|
||||
return view('admin.space.show', [
|
||||
'space' => $request->user()->space
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Space $space)
|
||||
{
|
||||
return view('admin.space.show', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.space.create', [
|
||||
'space' => new Space()
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->merge(['full_host' => $request->get('host') . '.' . config('app.root_domain')]);
|
||||
$request->validate([
|
||||
'domain' => 'required|unique:spaces|regex:/'. Space::DOMAIN_REGEX . '/',
|
||||
'host' => 'required|regex:/'. Space::HOST_REGEX . '/',
|
||||
'full_host' => 'required|unique:spaces,host',
|
||||
]);
|
||||
|
||||
$space = new Space();
|
||||
$space->domain = $request->get('domain');
|
||||
$space->host = $request->get('full_host');
|
||||
$space->save();
|
||||
|
||||
return redirect()->route('admin.spaces.index');
|
||||
}
|
||||
|
||||
public function edit(Space $space)
|
||||
{
|
||||
return view('admin.space.edit', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$space = $this->setConfig($request, $space);
|
||||
$space->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function parameters(Space $space)
|
||||
{
|
||||
return view('admin.space.parameters', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function parametersUpdate(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'max_accounts' => 'required|integer|min:0',
|
||||
'expire_at' => 'nullable|date|after_or_equal:today'
|
||||
]);
|
||||
|
||||
if ($request->get('max_accounts') > 0) {
|
||||
$request->validate([
|
||||
'max_accounts' => 'integer|min:' . $space->accounts()->count()
|
||||
]);
|
||||
}
|
||||
|
||||
$space->super = getRequestBoolean($request, 'super');
|
||||
$space->max_accounts = $request->get('max_accounts');
|
||||
$space->expire_at = $request->get('expire_at');
|
||||
$space->save();
|
||||
|
||||
return redirect()->route('admin.spaces.show', $space);
|
||||
}
|
||||
|
||||
private function setConfig(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$space->disable_chat_feature = getRequestBoolean($request, 'disable_chat_feature');
|
||||
$space->disable_meetings_feature = getRequestBoolean($request, 'disable_meetings_feature');
|
||||
$space->disable_broadcast_feature = getRequestBoolean($request, 'disable_broadcast_feature');
|
||||
$space->hide_settings = getRequestBoolean($request, 'hide_settings');
|
||||
$space->max_account = $request->get('max_account', 0);
|
||||
$space->hide_account_settings = getRequestBoolean($request, 'hide_account_settings');
|
||||
$space->disable_call_recordings_feature = getRequestBoolean($request, 'disable_call_recordings_feature');
|
||||
$space->only_display_sip_uri_username = getRequestBoolean($request, 'only_display_sip_uri_username');
|
||||
$space->assistant_hide_create_account = getRequestBoolean($request, 'assistant_hide_create_account');
|
||||
$space->assistant_disable_qr_code = getRequestBoolean($request, 'assistant_disable_qr_code');
|
||||
$space->assistant_hide_third_party_account = getRequestBoolean($request, 'assistant_hide_third_party_account');
|
||||
|
||||
return $space;
|
||||
}
|
||||
|
||||
public function delete(Request $request, int $id)
|
||||
{
|
||||
$space = Space::findOrFail($id);
|
||||
return view('admin.space.delete', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id)
|
||||
{
|
||||
$space = Space::findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'domain' => [
|
||||
'required',
|
||||
Rule::in(['first-zone', $space->domain]),
|
||||
]
|
||||
]);
|
||||
|
||||
$space->delete();
|
||||
|
||||
return redirect()->route('admin.spaces.index');
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +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;
|
||||
use App\Space;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
|
@ -139,14 +139,15 @@ class AccountController extends Controller
|
|||
|
||||
public function store(AsAdminRequest $request)
|
||||
{
|
||||
// Create the missing SipDomain
|
||||
if ($request->user()->superAdmin
|
||||
// Create the missing Space
|
||||
/*if ($request->user()->superAdmin
|
||||
&& $request->has('domain')
|
||||
&& !SipDomain::pluck('domain')->contains($request->get('domain'))) {
|
||||
$sipDomain = new SipDomain();
|
||||
$sipDomain->domain = $request->get('domain');
|
||||
$sipDomain->save();
|
||||
}
|
||||
&& !Space::pluck('domain')->contains($request->get('domain'))) {
|
||||
$space = new Space();
|
||||
$space->domain = $request->get('domain');
|
||||
$space->host = $request->get('host');
|
||||
$space->save();
|
||||
}*/
|
||||
|
||||
return (new AccountService())->store($request)->makeVisible(['confirmation_key', 'provisioning_token']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
<?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',
|
||||
]);
|
||||
|
||||
$sipDomain = new SipDomain;
|
||||
$sipDomain->domain = $request->get('domain');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'super');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'disable_chat_feature');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'disable_meetings_feature');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'disable_broadcast_feature');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'hide_settings');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'hide_account_settings');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'disable_call_recordings_feature');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'only_display_sip_uri_username');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'assistant_hide_create_account');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'assistant_disable_qr_code');
|
||||
$this->setRequestBoolean($request, $sipDomain, 'assistant_hide_third_party_account');
|
||||
$sipDomain->max_account = $request->get('max_account', 0);
|
||||
|
||||
$sipDomain->save();
|
||||
|
||||
return $sipDomain->refresh();
|
||||
}
|
||||
|
||||
public function show(string $domain)
|
||||
{
|
||||
return SipDomain::where('domain', $domain)->firstOrFail();
|
||||
}
|
||||
|
||||
public function update(Request $request, string $domain)
|
||||
{
|
||||
$request->validate([
|
||||
'super' => 'required|boolean',
|
||||
'disable_chat_feature' => 'required|boolean',
|
||||
'disable_meetings_feature' => 'required|boolean',
|
||||
'disable_broadcast_feature' => 'required|boolean',
|
||||
'hide_settings' => 'required|boolean',
|
||||
'hide_account_settings' => 'required|boolean',
|
||||
'disable_call_recordings_feature' => 'required|boolean',
|
||||
'only_display_sip_uri_username' => 'required|boolean',
|
||||
'assistant_hide_create_account' => 'required|boolean',
|
||||
'assistant_disable_qr_code' => 'required|boolean',
|
||||
'assistant_hide_third_party_account' => 'required|boolean',
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$sipDomain = SipDomain::where('domain', $domain)->firstOrFail();
|
||||
$sipDomain->super = $request->get('super');
|
||||
$sipDomain->disable_chat_feature = $request->get('disable_chat_feature');
|
||||
$sipDomain->disable_meetings_feature = $request->get('disable_meetings_feature');
|
||||
$sipDomain->disable_broadcast_feature = $request->get('disable_broadcast_feature');
|
||||
$sipDomain->hide_settings = $request->get('hide_settings');
|
||||
$sipDomain->hide_account_settings = $request->get('hide_account_settings');
|
||||
$sipDomain->disable_call_recordings_feature = $request->get('disable_call_recordings_feature');
|
||||
$sipDomain->only_display_sip_uri_username = $request->get('only_display_sip_uri_username');
|
||||
$sipDomain->assistant_hide_create_account = $request->get('assistant_hide_create_account');
|
||||
$sipDomain->assistant_disable_qr_code = $request->get('assistant_disable_qr_code');
|
||||
$sipDomain->assistant_hide_third_party_account = $request->get('assistant_hide_third_party_account');
|
||||
$sipDomain->max_account = $request->get('max_account', 0);
|
||||
$sipDomain->save();
|
||||
|
||||
return $sipDomain;
|
||||
}
|
||||
|
||||
private function setRequestBoolean(Request $request, SipDomain $sipDomain, string $key)
|
||||
{
|
||||
if ($request->has($key)) {
|
||||
$sipDomain->$key = (bool)$request->get($key);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $domain)
|
||||
{
|
||||
return SipDomain::where('domain', $domain)->delete();
|
||||
}
|
||||
}
|
||||
134
flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php
Normal file
134
flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<?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 App\Space;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SpaceController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Space::all();
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'domain' => 'required|unique:spaces',
|
||||
'host' => 'required|unique:spaces',
|
||||
'max_accounts' => 'nullable|integer',
|
||||
'expire_at' => 'nullable|date|after_or_equal:today'
|
||||
]);
|
||||
|
||||
$space = new Space;
|
||||
$space->domain = $request->get('domain');
|
||||
$space->host = $request->get('host');
|
||||
$this->setRequestBoolean($request, $space, 'super');
|
||||
$this->setRequestBoolean($request, $space, 'disable_chat_feature');
|
||||
$this->setRequestBoolean($request, $space, 'disable_meetings_feature');
|
||||
$this->setRequestBoolean($request, $space, 'disable_broadcast_feature');
|
||||
$this->setRequestBoolean($request, $space, 'hide_settings');
|
||||
$this->setRequestBoolean($request, $space, 'hide_account_settings');
|
||||
$this->setRequestBoolean($request, $space, 'disable_call_recordings_feature');
|
||||
$this->setRequestBoolean($request, $space, 'only_display_sip_uri_username');
|
||||
$this->setRequestBoolean($request, $space, 'assistant_hide_create_account');
|
||||
$this->setRequestBoolean($request, $space, 'assistant_disable_qr_code');
|
||||
$this->setRequestBoolean($request, $space, 'assistant_hide_third_party_account');
|
||||
$space->max_account = $request->get('max_account', 0);
|
||||
$space->max_accounts = $request->get('max_accounts', 0);
|
||||
$space->expire_at = $request->get('expire_at');
|
||||
|
||||
$space->save();
|
||||
|
||||
return $space->refresh();
|
||||
}
|
||||
|
||||
public function show(string $domain)
|
||||
{
|
||||
return Space::where('domain', $domain)->firstOrFail();
|
||||
}
|
||||
|
||||
public function update(Request $request, string $domain)
|
||||
{
|
||||
$request->validate([
|
||||
'super' => 'required|boolean',
|
||||
'disable_chat_feature' => 'required|boolean',
|
||||
'disable_meetings_feature' => 'required|boolean',
|
||||
'disable_broadcast_feature' => 'required|boolean',
|
||||
'hide_settings' => 'required|boolean',
|
||||
'hide_account_settings' => 'required|boolean',
|
||||
'disable_call_recordings_feature' => 'required|boolean',
|
||||
'only_display_sip_uri_username' => 'required|boolean',
|
||||
'assistant_hide_create_account' => 'required|boolean',
|
||||
'assistant_disable_qr_code' => 'required|boolean',
|
||||
'assistant_hide_third_party_account' => 'required|boolean',
|
||||
'max_account' => 'required|integer',
|
||||
'max_accounts' => 'required|integer',
|
||||
'expire_at' => 'nullable|date|after_or_equal:today',
|
||||
]);
|
||||
|
||||
$space = Space::where('domain', $domain)->firstOrFail();
|
||||
|
||||
if ($request->get('max_accounts') > 0) {
|
||||
$request->validate([
|
||||
'max_accounts' => 'integer|min:' . $space->accounts()->count()
|
||||
]);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'host' => ['required', Rule::unique('spaces')->ignore($space->id)]
|
||||
]);
|
||||
|
||||
$space->host = $request->get('host');
|
||||
$space->super = $request->get('super');
|
||||
$space->disable_chat_feature = $request->get('disable_chat_feature');
|
||||
$space->disable_meetings_feature = $request->get('disable_meetings_feature');
|
||||
$space->disable_broadcast_feature = $request->get('disable_broadcast_feature');
|
||||
$space->hide_settings = $request->get('hide_settings');
|
||||
$space->hide_account_settings = $request->get('hide_account_settings');
|
||||
$space->disable_call_recordings_feature = $request->get('disable_call_recordings_feature');
|
||||
$space->only_display_sip_uri_username = $request->get('only_display_sip_uri_username');
|
||||
$space->assistant_hide_create_account = $request->get('assistant_hide_create_account');
|
||||
$space->assistant_disable_qr_code = $request->get('assistant_disable_qr_code');
|
||||
$space->assistant_hide_third_party_account = $request->get('assistant_hide_third_party_account');
|
||||
$space->max_account = $request->get('max_account', 0);
|
||||
$space->max_accounts = $request->get('max_accounts', 0);
|
||||
$space->expire_at = $request->get('expire_at');
|
||||
$space->save();
|
||||
|
||||
return $space;
|
||||
}
|
||||
|
||||
private function setRequestBoolean(Request $request, Space $space, string $key)
|
||||
{
|
||||
if ($request->has($key)) {
|
||||
$space->$key = (bool)$request->get($key);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $domain)
|
||||
{
|
||||
return Space::where('domain', $domain)->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\Space::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -59,6 +60,7 @@ class Kernel extends HttpKernel
|
|||
'bindings',
|
||||
'validate_json',
|
||||
'localization',
|
||||
'space'
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -87,6 +89,8 @@ class Kernel extends HttpKernel
|
|||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'space' => \App\Http\Middleware\Space::class,
|
||||
'space.expired' => \App\Http\Middleware\IsSpaceExpired::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'localization' => \App\Http\Middleware\Localization::class,
|
||||
|
|
|
|||
19
flexiapi/app/Http/Middleware/IsSpaceExpired.php
Normal file
19
flexiapi/app/Http/Middleware/IsSpaceExpired.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IsSpaceExpired
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->user() && !$request->user()->superAdmin && $request->get('resolvedSpace')?->isExpired()) {
|
||||
abort(403, 'The related Space has expired');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
35
flexiapi/app/Http/Middleware/Space.php
Normal file
35
flexiapi/app/Http/Middleware/Space.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Space
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (empty(config('app.root_domain'))) {
|
||||
return abort(503, 'APP_ROOT_DOMAIN is not configured');
|
||||
}
|
||||
|
||||
$space = \App\Space::where('host', $request->header('host'))->first();
|
||||
|
||||
if ($space) {
|
||||
if (!str_ends_with($space->host, config('app.root_domain'))) {
|
||||
return abort(503, 'The APP_ROOT_DOMAIN configured does not match with the current root domain');
|
||||
}
|
||||
|
||||
Config::set('app.url', '://' . $space->host);
|
||||
Config::set('app.sip_domain', $space->domain);
|
||||
|
||||
$request->request->set('resolvedSpace', $space);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return abort(404, 'Host not configured');
|
||||
}
|
||||
}
|
||||
|
|
@ -29,11 +29,6 @@ class AsAdminRequest extends Request
|
|||
{
|
||||
use RequestsApi, AsAdmin;
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
@ -46,10 +41,6 @@ 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'],
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ class Request extends CreateRequest
|
|||
{
|
||||
use RequestsApi;
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use Illuminate\Foundation\Http\FormRequest;
|
|||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Account;
|
||||
use App\Space;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\Dictionary;
|
||||
use App\Rules\FilteredPhone;
|
||||
|
|
@ -34,7 +35,8 @@ class Request extends FormRequest
|
|||
{
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
$space = Space::where('domain', resolveDomain($this))->first();
|
||||
return ($space && !$space->isFull());
|
||||
}
|
||||
|
||||
public function rules()
|
||||
|
|
@ -54,7 +56,7 @@ class Request extends FormRequest
|
|||
}),
|
||||
'filled',
|
||||
],
|
||||
'domain' => 'exists:sip_domains,domain',
|
||||
'domain' => 'exists:spaces,domain',
|
||||
'dictionary' => [new Dictionary()],
|
||||
'password' => 'required|min:3',
|
||||
'email' => config('app.account_email_unique')
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ class AsAdminRequest extends CreateRequest
|
|||
{
|
||||
use AsAdmin;
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
|
|||
|
|
@ -23,11 +23,6 @@ use App\Http\Requests\Account\Create\Request as CreateRequest;
|
|||
|
||||
class Request extends CreateRequest
|
||||
{
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ class AsAdminRequest extends UpdateRequest
|
|||
{
|
||||
use RequestsApi, AsAdmin;
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Request extends FormRequest
|
|||
})->ignore($this->route('account_id'), 'id'),
|
||||
'filled',
|
||||
],
|
||||
'domain' => 'exists:sip_domains,domain',
|
||||
'domain' => 'exists:spaces,domain',
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email',
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ class AsAdminRequest extends UpdateRequest
|
|||
{
|
||||
use AsAdmin;
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = parent::rules();
|
||||
|
|
|
|||
|
|
@ -15,11 +15,5 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
Validator::extend('iso_date', 'validateIsoDate');
|
||||
|
||||
if (!empty(config('app.url'))) {
|
||||
// Add following lines to force laravel to use APP_URL as root url for the app.
|
||||
$strBaseURL = $this->app['url'];
|
||||
$strBaseURL->forceRootUrl(config('app.url'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ class RouteServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
|
||||
parent::boot();
|
||||
}
|
||||
|
||||
|
|
@ -43,10 +41,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
public function map()
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
|
||||
$this->mapWebRoutes();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SipDomain extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id'];
|
||||
protected $casts = [
|
||||
'super' => 'boolean',
|
||||
'disable_chat_feature' => 'boolean',
|
||||
'disable_meetings_feature' => 'boolean',
|
||||
'disable_broadcast_feature' => 'boolean',
|
||||
'hide_settings' => 'boolean',
|
||||
'hide_account_settings' => 'boolean',
|
||||
'disable_call_recordings_feature' => 'boolean',
|
||||
'only_display_sip_uri_username' => 'boolean',
|
||||
'assistant_hide_create_account' => 'boolean',
|
||||
'assistant_disable_qr_code' => 'boolean',
|
||||
'assistant_hide_third_party_account' => 'boolean',
|
||||
];
|
||||
|
||||
public function accounts()
|
||||
{
|
||||
return $this->hasMany(Account::class, 'domain', 'domain');
|
||||
}
|
||||
}
|
||||
89
flexiapi/app/Space.php
Normal file
89
flexiapi/app/Space.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Space extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id'];
|
||||
protected $casts = [
|
||||
'super' => 'boolean',
|
||||
'disable_chat_feature' => 'boolean',
|
||||
'disable_meetings_feature' => 'boolean',
|
||||
'disable_broadcast_feature' => 'boolean',
|
||||
'hide_settings' => 'boolean',
|
||||
'hide_account_settings' => 'boolean',
|
||||
'disable_call_recordings_feature' => 'boolean',
|
||||
'only_display_sip_uri_username' => 'boolean',
|
||||
'assistant_hide_create_account' => 'boolean',
|
||||
'assistant_disable_qr_code' => 'boolean',
|
||||
'assistant_hide_third_party_account' => 'boolean',
|
||||
'expire_at' => 'date',
|
||||
];
|
||||
|
||||
public const HOST_REGEX = '[\w\-]+';
|
||||
public const DOMAIN_REGEX = '[\w\-\.]+';
|
||||
|
||||
public function accounts()
|
||||
{
|
||||
return $this->hasMany(Account::class, 'domain', 'domain');
|
||||
}
|
||||
|
||||
public function admins()
|
||||
{
|
||||
return $this->accounts()->where('admin', true);
|
||||
}
|
||||
|
||||
public function scopeNotFull(Builder $query)
|
||||
{
|
||||
return $query->where('max_accounts', 0)
|
||||
->orWhereRaw('max_accounts > (select count(*) from accounts where domain = spaces.domain)');
|
||||
}
|
||||
|
||||
public function getAccountsPercentageAttribute(): int
|
||||
{
|
||||
if ($this->max_accounts != null) {
|
||||
return (int)($this->accounts()->count() / $this->max_accounts * 100);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isFull(): bool
|
||||
{
|
||||
return $this->max_accounts > 0 && ($this->accounts()->count() >= $this->max_accounts);
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->expire_at && $this->expire_at->isPast();
|
||||
}
|
||||
|
||||
public function getAccountsPercentageClassAttribute(): string
|
||||
{
|
||||
if ($this->getAccountsPercentageAttribute() >= 80) {
|
||||
return 'orange';
|
||||
}
|
||||
|
||||
if ($this->getAccountsPercentageAttribute() >= 60) {
|
||||
return 'yellow';
|
||||
}
|
||||
|
||||
return 'green';
|
||||
}
|
||||
|
||||
public function getDaysLeftAttribute(): ?int
|
||||
{
|
||||
if ($this->expire_at != null) {
|
||||
return (int)$this->expire_at->diffInDays(Carbon::now());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
106
flexiapi/composer.lock
generated
106
flexiapi/composer.lock
generated
|
|
@ -1191,16 +1191,16 @@
|
|||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
"version": "v1.24.0",
|
||||
"version": "v1.24.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FakerPHP/Faker.git",
|
||||
"reference": "a136842a532bac9ecd8a1c723852b09915d7db50"
|
||||
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50",
|
||||
"reference": "a136842a532bac9ecd8a1c723852b09915d7db50",
|
||||
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
|
||||
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1248,9 +1248,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/FakerPHP/Faker/issues",
|
||||
"source": "https://github.com/FakerPHP/Faker/tree/v1.24.0"
|
||||
"source": "https://github.com/FakerPHP/Faker/tree/v1.24.1"
|
||||
},
|
||||
"time": "2024-11-07T15:11:20+00:00"
|
||||
"time": "2024-11-21T13:46:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
|
|
@ -1325,16 +1325,16 @@
|
|||
},
|
||||
{
|
||||
"name": "giggsey/libphonenumber-for-php-lite",
|
||||
"version": "8.13.49",
|
||||
"version": "8.13.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/giggsey/libphonenumber-for-php-lite.git",
|
||||
"reference": "f2bc102ccd614fde128b463273ce9843e14bf430"
|
||||
"reference": "57bb2bfd8d4a9896ed961c584141247f2a35bc04"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/f2bc102ccd614fde128b463273ce9843e14bf430",
|
||||
"reference": "f2bc102ccd614fde128b463273ce9843e14bf430",
|
||||
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/57bb2bfd8d4a9896ed961c584141247f2a35bc04",
|
||||
"reference": "57bb2bfd8d4a9896ed961c584141247f2a35bc04",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1404,7 +1404,7 @@
|
|||
"issues": "https://github.com/giggsey/libphonenumber-for-php-lite/issues",
|
||||
"source": "https://github.com/giggsey/libphonenumber-for-php-lite"
|
||||
},
|
||||
"time": "2024-11-04T10:42:41+00:00"
|
||||
"time": "2024-11-18T09:58:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
|
|
@ -1881,16 +1881,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v10.48.23",
|
||||
"version": "v10.48.25",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "625269ca4881d2b50eded2045cb930960a181d98"
|
||||
"reference": "f132b23b13909cc22c615c01b0c5640541c3da0c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/625269ca4881d2b50eded2045cb930960a181d98",
|
||||
"reference": "625269ca4881d2b50eded2045cb930960a181d98",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/f132b23b13909cc22c615c01b0c5640541c3da0c",
|
||||
"reference": "f132b23b13909cc22c615c01b0c5640541c3da0c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1997,7 +1997,7 @@
|
|||
"nyholm/psr7": "^1.2",
|
||||
"orchestra/testbench-core": "^8.23.4",
|
||||
"pda/pheanstalk": "^4.0",
|
||||
"phpstan/phpstan": "^1.4.7",
|
||||
"phpstan/phpstan": "~1.11.11",
|
||||
"phpunit/phpunit": "^10.0.7",
|
||||
"predis/predis": "^2.0.2",
|
||||
"symfony/cache": "^6.2",
|
||||
|
|
@ -2084,7 +2084,7 @@
|
|||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2024-11-12T15:39:10+00:00"
|
||||
"time": "2024-11-26T15:32:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
|
|
@ -2146,16 +2146,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.3.6",
|
||||
"version": "v1.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/serializable-closure.git",
|
||||
"reference": "f865a58ea3a0107c336b7045104c75243fa59d96"
|
||||
"reference": "4f48ade902b94323ca3be7646db16209ec76be3d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96",
|
||||
"reference": "f865a58ea3a0107c336b7045104c75243fa59d96",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d",
|
||||
"reference": "4f48ade902b94323ca3be7646db16209ec76be3d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2203,7 +2203,7 @@
|
|||
"issues": "https://github.com/laravel/serializable-closure/issues",
|
||||
"source": "https://github.com/laravel/serializable-closure"
|
||||
},
|
||||
"time": "2024-11-11T17:06:04+00:00"
|
||||
"time": "2024-11-14T18:34:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
|
|
@ -3394,32 +3394,32 @@
|
|||
},
|
||||
{
|
||||
"name": "nunomaduro/termwind",
|
||||
"version": "v1.16.0",
|
||||
"version": "v1.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nunomaduro/termwind.git",
|
||||
"reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257"
|
||||
"reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dcf1ec3dfa36137b7ce41d43866644a7ab8fc257",
|
||||
"reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257",
|
||||
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301",
|
||||
"reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^8.1",
|
||||
"symfony/console": "^6.4.12"
|
||||
"symfony/console": "^6.4.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/console": "^10.48.22",
|
||||
"illuminate/support": "^10.48.22",
|
||||
"laravel/pint": "^1.18.1",
|
||||
"pestphp/pest": "^2",
|
||||
"illuminate/console": "^10.48.24",
|
||||
"illuminate/support": "^10.48.24",
|
||||
"laravel/pint": "^1.18.2",
|
||||
"pestphp/pest": "^2.36.0",
|
||||
"pestphp/pest-plugin-mock": "2.0.0",
|
||||
"phpstan/phpstan": "^1.12.6",
|
||||
"phpstan/phpstan": "^1.12.11",
|
||||
"phpstan/phpstan-strict-rules": "^1.6.1",
|
||||
"symfony/var-dumper": "^6.4.11",
|
||||
"symfony/var-dumper": "^6.4.15",
|
||||
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
|
|
@ -3459,7 +3459,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nunomaduro/termwind/issues",
|
||||
"source": "https://github.com/nunomaduro/termwind/tree/v1.16.0"
|
||||
"source": "https://github.com/nunomaduro/termwind/tree/v1.17.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3475,7 +3475,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-15T15:27:12+00:00"
|
||||
"time": "2024-11-21T10:36:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ovh/ovh",
|
||||
|
|
@ -5599,16 +5599,16 @@
|
|||
},
|
||||
{
|
||||
"name": "respect/validation",
|
||||
"version": "2.3.7",
|
||||
"version": "2.3.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Respect/Validation.git",
|
||||
"reference": "967f7b6cc71e3728bb0f766cc1aea0604b2955aa"
|
||||
"reference": "25ce44c7ee9613d260c7c0e44e27daa2131f383a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Respect/Validation/zipball/967f7b6cc71e3728bb0f766cc1aea0604b2955aa",
|
||||
"reference": "967f7b6cc71e3728bb0f766cc1aea0604b2955aa",
|
||||
"url": "https://api.github.com/repos/Respect/Validation/zipball/25ce44c7ee9613d260c7c0e44e27daa2131f383a",
|
||||
"reference": "25ce44c7ee9613d260c7c0e44e27daa2131f383a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5661,9 +5661,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Respect/Validation/issues",
|
||||
"source": "https://github.com/Respect/Validation/tree/2.3.7"
|
||||
"source": "https://github.com/Respect/Validation/tree/2.3.8"
|
||||
},
|
||||
"time": "2024-04-13T09:45:55+00:00"
|
||||
"time": "2024-11-26T09:14:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/uri",
|
||||
|
|
@ -9220,16 +9220,16 @@
|
|||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/voku/portable-ascii.git",
|
||||
"reference": "b56450eed252f6801410d810c8e1727224ae0743"
|
||||
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743",
|
||||
"reference": "b56450eed252f6801410d810c8e1727224ae0743",
|
||||
"url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
|
||||
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -9254,7 +9254,7 @@
|
|||
"authors": [
|
||||
{
|
||||
"name": "Lars Moelleken",
|
||||
"homepage": "http://www.moelleken.org/"
|
||||
"homepage": "https://www.moelleken.org/"
|
||||
}
|
||||
],
|
||||
"description": "Portable ASCII library - performance optimized (ascii) string functions for php.",
|
||||
|
|
@ -9266,7 +9266,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/voku/portable-ascii/issues",
|
||||
"source": "https://github.com/voku/portable-ascii/tree/2.0.1"
|
||||
"source": "https://github.com/voku/portable-ascii/tree/2.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -9290,7 +9290,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-08T17:03:00+00:00"
|
||||
"time": "2024-11-21T01:49:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
|
|
@ -9946,16 +9946,16 @@
|
|||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.11.0",
|
||||
"version": "3.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
|
||||
"reference": "70c08f8d20c0eb4fe56f26644dd94dae76a7f450"
|
||||
"reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/70c08f8d20c0eb4fe56f26644dd94dae76a7f450",
|
||||
"reference": "70c08f8d20c0eb4fe56f26644dd94dae76a7f450",
|
||||
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87",
|
||||
"reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -10022,7 +10022,7 @@
|
|||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-12T09:53:29+00:00"
|
||||
"time": "2024-11-16T12:02:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ return [
|
|||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
'root_domain' => env('APP_ROOT_DOMAIN', null),
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -66,19 +66,10 @@ return [
|
|||
*/
|
||||
|
||||
'providers' => [
|
||||
/*'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\User::class,
|
||||
],*/
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Account::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -26,7 +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;
|
||||
use App\Space;
|
||||
|
||||
class AccountFactory extends Factory
|
||||
{
|
||||
|
|
@ -35,9 +35,9 @@ class AccountFactory extends Factory
|
|||
|
||||
public function definition()
|
||||
{
|
||||
$domain = SipDomain::count() == 0
|
||||
? SipDomain::factory()->create()
|
||||
: SipDomain::first();
|
||||
$domain = Space::count() == 0
|
||||
? Space::factory()->create()
|
||||
: Space::first();
|
||||
|
||||
return [
|
||||
'username' => $this->faker->username,
|
||||
|
|
@ -53,6 +53,13 @@ class AccountFactory extends Factory
|
|||
];
|
||||
}
|
||||
|
||||
public function fromSpace(Space $space)
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'domain' => $space->domain
|
||||
]);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
|
|
@ -63,9 +70,9 @@ 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();
|
||||
$space = Space::where('domain', $attributes['domain'])->first();
|
||||
$space->super = true;
|
||||
$space->save();
|
||||
|
||||
return [
|
||||
'admin' => true,
|
||||
|
|
|
|||
|
|
@ -19,17 +19,19 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class SipDomainFactory extends Factory
|
||||
class SpaceFactory extends Factory
|
||||
{
|
||||
protected $model = SipDomain::class;
|
||||
protected $model = Space::class;
|
||||
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'domain' => config('app.sip_domain'),
|
||||
'host' => config('app.sip_domain'),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +39,14 @@ class SipDomainFactory extends Factory
|
|||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'domain' => 'second_' . config('app.sip_domain'),
|
||||
'host' => 'second_' . config('app.sip_domain'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function expired()
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'expire_at' => Carbon::today()->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
@ -18,10 +18,10 @@ return new class extends Migration
|
|||
});
|
||||
|
||||
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();
|
||||
$space = new Space;
|
||||
$space->domain = $domain;
|
||||
$space->super = env('APP_ADMINS_MANAGE_MULTI_DOMAINS', false); // historical environnement boolean
|
||||
$space->save();
|
||||
}
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
use App\Space;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::rename('sip_domains', 'spaces');
|
||||
|
||||
Schema::table('spaces', function (Blueprint $table) {
|
||||
$table->string('host')->unique()->nullable();
|
||||
$table->integer('max_accounts')->default(0);
|
||||
$table->datetime('expire_at')->nullable();
|
||||
});
|
||||
|
||||
DB::statement("update spaces set host = domain");
|
||||
|
||||
Schema::table('spaces', function (Blueprint $table) {
|
||||
$table->string('host')->nullable(false)->change();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::rename('spaces', 'sip_domains');
|
||||
Schema::table('sip_domains', function (Blueprint $table) {
|
||||
$table->dropColumn('host');
|
||||
$table->dropColumn('max_accounts');
|
||||
$table->dropColumn('expire_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
namespace Database\Seeders;
|
||||
|
||||
use App\Account;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
|
@ -92,9 +92,9 @@ class LiblinphoneTesterAccoutSeeder extends Seeder
|
|||
|
||||
// Create the domains
|
||||
foreach ($domains as $domain) {
|
||||
$sipDomain = SipDomain::where('domain', $domain)->firstOrNew();
|
||||
$sipDomain->domain = $domain;
|
||||
$sipDomain->save();
|
||||
$space = Space::where('domain', $domain)->firstOrNew();
|
||||
$space->domain = $domain;
|
||||
$space->save();
|
||||
}
|
||||
|
||||
// And seed the fresh ones
|
||||
|
|
|
|||
87
flexiapi/public/css/form.css
vendored
87
flexiapi/public/css/form.css
vendored
|
|
@ -7,12 +7,18 @@
|
|||
border: 1px solid var(--main-5);
|
||||
border-radius: 3rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
padding: 1rem 2rem;
|
||||
line-height: 4rem;
|
||||
padding: 0 2rem;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn.small {
|
||||
line-height: 3rem;
|
||||
padding: 0rem 2rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
p .btn {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
|
@ -94,7 +100,6 @@ form.inline {
|
|||
|
||||
form div {
|
||||
position: relative;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
form div .btn,
|
||||
|
|
@ -202,10 +207,6 @@ form div select {
|
|||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
form div.checkbox {
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
form div.search:after,
|
||||
form div.select:after {
|
||||
font-family: 'Phosphor';
|
||||
|
|
@ -228,6 +229,15 @@ form div.search:after {
|
|||
content: "\e30c";
|
||||
}
|
||||
|
||||
form div span.supporting {
|
||||
line-height: 2rem;
|
||||
font-size: 1.25rem;
|
||||
color: var(--grey-4);
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
form div input[disabled],
|
||||
form div textarea[disabled] {
|
||||
border-color: var(--grey-4);
|
||||
|
|
@ -324,3 +334,66 @@ form div input:invalid:not(:placeholder-shown)+label,
|
|||
form div textarea:invalid:not(:placeholder-shown)+label {
|
||||
color: var(--danger-5);
|
||||
}
|
||||
|
||||
/* Checkbox element */
|
||||
|
||||
form div.checkbox {
|
||||
padding: 0.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form div.checkbox > div {
|
||||
margin-right: 5rem;
|
||||
}
|
||||
|
||||
div.checkbox > input[type="checkbox"] {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.checkbox p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.checkbox::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 3rem;
|
||||
width: 5rem;
|
||||
background-color: var(--grey-3);
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s ease;
|
||||
top: calc(50% - 1.5rem);
|
||||
right: 0;
|
||||
border-radius: 4rem;
|
||||
}
|
||||
|
||||
div.checkbox:has(> input[type="checkbox"]:checked)::before {
|
||||
background-color: var(--color-green);
|
||||
}
|
||||
|
||||
div.checkbox > input[type="checkbox"] + label {
|
||||
display: block;
|
||||
background-color: white;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 2rem;
|
||||
top: calc(50% - 1rem);
|
||||
right: 2.5rem;
|
||||
position: absolute;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
line-height: normal;
|
||||
transition: right 0.3s ease;
|
||||
}
|
||||
|
||||
div.checkbox:hover > input[type="checkbox"] + label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.checkbox > input[type="checkbox"]:checked + label {
|
||||
right: 0.5rem;
|
||||
}
|
||||
59
flexiapi/public/css/style.css
vendored
59
flexiapi/public/css/style.css
vendored
|
|
@ -340,28 +340,42 @@ content section {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
content section.grid {
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.grid.third {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
content section.grid {
|
||||
.grid {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid.third {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
content section.grid header,
|
||||
content section.grid .large {
|
||||
.grid header,
|
||||
.grid .large {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.grid.third header,
|
||||
.grid.third .large {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
content section header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-bottom: 3rem;
|
||||
flex-flow: wrap;
|
||||
}
|
||||
|
||||
content section header p {
|
||||
|
|
@ -381,6 +395,12 @@ content nav+section {
|
|||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
content nav+section {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sidenav **/
|
||||
|
||||
content>nav {
|
||||
|
|
@ -417,7 +437,7 @@ content>nav a.current {
|
|||
background-color: white;
|
||||
border-radius: 4rem;
|
||||
color: var(--main-5);
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
content>nav a i {
|
||||
|
|
@ -758,11 +778,16 @@ select.list_toggle {
|
|||
.card {
|
||||
background-color: var(--grey-1);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card .icon {
|
||||
font-size: 3rem;
|
||||
color: var(--second-6);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
|
|
@ -805,6 +830,28 @@ select.list_toggle {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar,
|
||||
progress::-moz-progress-bar {
|
||||
background-color: var(--color-green);
|
||||
}
|
||||
|
||||
progress.yellow::-webkit-progress-bar,
|
||||
progress.yellow::-moz-progress-bar {
|
||||
background-color: var(--color-yellow);
|
||||
}
|
||||
|
||||
progress.orange::-webkit-progress-bar,
|
||||
progress.orange::-moz-progress-bar {
|
||||
background-color: var(--color-orange);
|
||||
}
|
||||
|
||||
/* Steps */
|
||||
|
||||
ol.steps {
|
||||
|
|
|
|||
12
flexiapi/public/scripts/utils.js
vendored
12
flexiapi/public/scripts/utils.js
vendored
|
|
@ -115,3 +115,15 @@ function digitFilled(element) {
|
|||
element.nextElementSibling.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function copyValueTo(from, to, append) {
|
||||
if (to.value == '') {
|
||||
let value = from.value;
|
||||
|
||||
if (append) {
|
||||
value += append;
|
||||
}
|
||||
|
||||
to.value = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">gauge</i> Dashboard</h1>
|
||||
<h1><i class="ph">gauge</i> My Account</h1>
|
||||
</header>
|
||||
|
||||
<div class="card">
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
<p><i class="ph">envelope</i> SIP address: sip:{{ $account->identifier }}</p>
|
||||
<p><i class="ph">user</i> Username: {{ $account->username }}</p>
|
||||
<p><i class="ph">hard-drives</i> Domain: {{ $account->domain }}</p>
|
||||
<p><i class="ph">globe-hemisphere-west</i> Domain: {{ $account->domain }}</p>
|
||||
|
||||
@if (!empty(config('app.proxy_registrar_address')))
|
||||
<p><i class="ph">lan</i> Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@
|
|||
</div>
|
||||
<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>
|
||||
@foreach ($domains as $space)
|
||||
<option value="{{ $space->domain }}" @if ($account->domain == $space->domain) selected="selected" @endif>
|
||||
{{ $space->domain }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="domain">Domain</label>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">users</i> Accounts</h1>
|
||||
@if ($space)
|
||||
<p>{{ $accounts->count()}} / @if ($space->max_accounts > 0){{ $space->max_accounts }} @else <i class="ph">infinity</i>@endif</p>
|
||||
@endif
|
||||
<a class="btn btn-secondary oppose" href="{{ route('admin.account.import.create') }}">
|
||||
<i class="ph">download-simple</i>
|
||||
Import Accounts
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
@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">hard-drives</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="ph">trash</i>
|
||||
Delete
|
||||
</a>
|
||||
<input form="create_edit_sip_domains" class="btn" type="submit" value="Update">
|
||||
@else
|
||||
<h1><i class="ph">user-rectangle</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
|
||||
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'super', 'label' => 'Super domain'])
|
||||
|
||||
<h3 class="large">Features</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'disable_chat_feature', 'label' => 'Chat feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'disable_meetings_feature', 'label' => 'Meeting feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'disable_broadcast_feature', 'label' => 'Conference feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'hide_settings', 'label' => 'General settings', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'hide_account_settings', 'label' => 'Account settings', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'disable_call_recordings_feature', 'label' => 'Record audio/video calls', 'reverse' => true])
|
||||
|
||||
<h3 class="large">General toggles</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'only_display_sip_uri_username', 'label' => 'Only display usernames (hide SIP addresses)'])
|
||||
|
||||
<div class="select">
|
||||
<select name="max_account">
|
||||
@foreach ([0 => 'No limit', 1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four', 5 => 'Five'] as $key => $value)
|
||||
<option value="{{ $key }}" @if ($sip_domain->max_account == $key) selected="selected" @endif>
|
||||
{{ $value }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="domain">Max account authorized</label>
|
||||
</div>
|
||||
|
||||
<h3 class="large">Assistant</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'assistant_hide_create_account', 'label' => 'Account creation panel', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'assistant_disable_qr_code', 'label' => 'QR Code scanning panel', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $sip_domain, 'key' => 'assistant_hide_third_party_account', 'label' => 'Third party SIP panel', 'reverse' => true])
|
||||
|
||||
</form>
|
||||
@endsection
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
SIP Domains
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<header>
|
||||
<h1><i class="ph">hard-drives</i> SIP Domains</h1>
|
||||
<a class="btn oppose" href="{{ route('admin.sip_domains.create') }}">
|
||||
<i class="ph">plus</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
|
||||
46
flexiapi/resources/views/admin/space/create.blade.php
Normal file
46
flexiapi/resources/views/admin/space/create.blade.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.spaces.index') }}">Spaces</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Create</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">user-rectangle</i> Create a Space</h1>
|
||||
<a href="{{ route('admin.spaces.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
</header>
|
||||
|
||||
<form method="POST"
|
||||
action="{{ route('admin.spaces.store') }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('post')
|
||||
|
||||
<div class="large">
|
||||
<input placeholder="subdomain" required="required" name="host" type="text" pattern="{{ $space::HOST_REGEX}}" style="width: 60%"
|
||||
value="{{ $space->host ?? old('host') }}" onchange="copyValueTo(this, this.form.querySelector('input[name=domain]'), '.{{ config('app.root_domain') }}')">
|
||||
<input placeholder=".{{ config('app.root_domain') }}" style="position: absolute; width: calc(40% - 1rem); margin-left: 1rem;" disabled>
|
||||
<label for="username">Subdomain</label>
|
||||
@include('parts.errors', ['name' => 'host'])
|
||||
@include('parts.errors', ['name' => 'full_host'])
|
||||
<span class="supporting">Cannot be changed once created</span>
|
||||
</div>
|
||||
|
||||
<div class="large">
|
||||
<input placeholder="domain.sip" required="required" name="domain" type="text" pattern="{{ $space::DOMAIN_REGEX}}" value="{{ $space->domain ?? old('domain') }}">
|
||||
<label for="username">SIP Domain</label>
|
||||
@include('parts.errors', ['name' => 'domain'])
|
||||
<span class="supporting">Cannot be changed once created</span>
|
||||
</div>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'super', 'label' => 'Super space', 'supporting' => 'All the admins in this Space will be Super Admins'])
|
||||
|
||||
<div class="large">
|
||||
<input class="btn" type="submit" value="Create">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@endsection
|
||||
|
|
@ -2,27 +2,26 @@
|
|||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.sip_domains.index') }}">SIP Domains</a>
|
||||
<a href="{{ route('admin.spaces.index') }}">Spaces</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Delete</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">trash</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">
|
||||
<h1><i class="ph">trash</i> Delete a Space</h1>
|
||||
<a href="{{ route('admin.spaces.edit', $space->id) }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
</header>
|
||||
<form id="delete" method="POST" action="{{ route('admin.sip_domains.destroy', $sip_domain->id) }}" accept-charset="UTF-8">
|
||||
<form id="delete" method="POST" action="{{ route('admin.spaces.destroy', $space) }}" 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>
|
||||
<h3>{{ $space->domain }}</h3>
|
||||
<p>This will also destroy <b>{{ $space->accounts()->count() }} related accounts</b></p>
|
||||
|
||||
<input name="sip_domain" type="hidden" value="{{ $sip_domain->id }}">
|
||||
<input name="sip_domain" type="hidden" value="{{ $space->id }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -30,5 +29,9 @@
|
|||
<label for="username">Please retype the domain here to confirm</label>
|
||||
@include('parts.errors', ['name' => 'domain'])
|
||||
</div>
|
||||
|
||||
<div class="large">
|
||||
<input class="btn" type="submit" value="Delete">
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
57
flexiapi/resources/views/admin/space/edit.blade.php
Normal file
57
flexiapi/resources/views/admin/space/edit.blade.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.spaces.index') }}">Spaces</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">{{ $space->host }}</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Configuration</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">globe-hemisphere-west</i> {{ $space->host }}</h1>
|
||||
</header>
|
||||
|
||||
@include('admin.space.tabs')
|
||||
|
||||
<form method="POST"
|
||||
action="{{ route('admin.spaces.update', $space->id) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('put')
|
||||
|
||||
<h3 class="large">Features</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'disable_chat_feature', 'label' => 'Chat feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'disable_meetings_feature', 'label' => 'Meeting feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'disable_broadcast_feature', 'label' => 'Conference feature', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'hide_settings', 'label' => 'General settings', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'hide_account_settings', 'label' => 'Account settings', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'disable_call_recordings_feature', 'label' => 'Record audio/video calls', 'reverse' => true])
|
||||
|
||||
<h3 class="large">General toggles</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'only_display_sip_uri_username', 'label' => 'Only display usernames (hide SIP addresses)'])
|
||||
|
||||
<div class="select">
|
||||
<select name="max_account">
|
||||
@foreach ([0 => 'No limit', 1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four', 5 => 'Five'] as $key => $value)
|
||||
<option value="{{ $key }}" @if ($space->max_account == $key) selected="selected" @endif>
|
||||
{{ $value }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="domain">Max account authorized</label>
|
||||
</div>
|
||||
|
||||
<h3 class="large">Assistant</h3>
|
||||
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'assistant_hide_create_account', 'label' => 'Account creation panel', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'assistant_disable_qr_code', 'label' => 'QR Code scanning panel', 'reverse' => true])
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'assistant_hide_third_party_account', 'label' => 'Third party SIP panel', 'reverse' => true])
|
||||
|
||||
<div class="large">
|
||||
<input class="btn" type="submit" value="Update">
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
55
flexiapi/resources/views/admin/space/index.blade.php
Normal file
55
flexiapi/resources/views/admin/space/index.blade.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
Spaces
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<header>
|
||||
<h1><i class="ph">globe-hemisphere-west</i> Spaces</h1>
|
||||
<a class="btn oppose" href="{{ route('admin.spaces.create') }}">
|
||||
<i class="ph">plus</i>
|
||||
New Space
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Space</th>
|
||||
<th>SIP Domain</th>
|
||||
<th>Accounts</th>
|
||||
<th>Expiration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($spaces as $space)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ route('admin.spaces.show', $space->id) }}">
|
||||
{{ $space->host }}
|
||||
@if ($space->super) <span class="badge badge-error" title="Super domain">Super</span> @endif
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ $space->domain }}</td>
|
||||
<td>
|
||||
{{ $space->accounts_count }} / @if ($space->max_accounts > 0){{ $space->max_accounts }} @else <i class="ph">infinity</i>@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($space->isExpired())
|
||||
Expired
|
||||
@elseif ($space->expire_at)
|
||||
In {{ $space->daysLeft }} days
|
||||
@else
|
||||
<i class="ph">infinity</i>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@endsection
|
||||
45
flexiapi/resources/views/admin/space/parameters.blade.php
Normal file
45
flexiapi/resources/views/admin/space/parameters.blade.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.spaces.index') }}">Spaces</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">{{ $space->host }}</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Parameters</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">globe-hemisphere-west</i> {{ $space->host }}</h1>
|
||||
</header>
|
||||
|
||||
@include('admin.space.tabs')
|
||||
|
||||
<form method="POST"
|
||||
action="{{ route('admin.spaces.parameters.update', $space) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('put')
|
||||
|
||||
<div>
|
||||
<input name="max_accounts" id="max_accounts" type="number" min="0" value="{{ $space->max_accounts }}">
|
||||
<label for="max_accounts">Max accounts of the space</label>
|
||||
<span class="supporting">Unlimited if set to 0</span>
|
||||
@include('parts.errors', ['name' => 'max_accounts'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input name="expire_at" id="expire_at" type="date" @if ($space->expire_at) value="{{ $space->expire_at->toDateString() }}" @endif min="{{ \Carbon\Carbon::now()->toDateString() }}">
|
||||
<label for="expire_at">Expire at</label>
|
||||
<span class="supporting">Clear to never expire</span>
|
||||
</div>
|
||||
|
||||
<div class="large">
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'super', 'label' => 'Super space', 'supporting' => 'All the admins in this Space will be Super Admins'])
|
||||
</div>
|
||||
|
||||
<div class="large">
|
||||
<input class="btn" type="submit" value="Update">
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
82
flexiapi/resources/views/admin/space/show.blade.php
Normal file
82
flexiapi/resources/views/admin/space/show.blade.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.spaces.index') }}">Spaces</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">{{ $space->host }}</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Information</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph">globe-hemisphere-west</i> {{ $space->host }}</h1>
|
||||
<a class="btn btn-tertiary oppose" href="{{ route('admin.spaces.delete', $space->id) }}">
|
||||
<i class="ph">trash</i>
|
||||
Delete
|
||||
</a>
|
||||
|
||||
<a class="btn btn-secondary" @if ($space->isFull())disabled @endif href="{{ route('admin.account.create', ['domain' => $space->domain]) }}">
|
||||
<i class="ph">user-plus</i> New Account
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@include('admin.space.tabs')
|
||||
|
||||
<div class="grid third ">
|
||||
<div class="card">
|
||||
<span class="icon"><i class="ph">users</i></span>
|
||||
<h3>Accounts</h3>
|
||||
@if ($space->max_accounts > 0)
|
||||
<progress max="100" value="{{ $space->accountsPercentage }}"
|
||||
class="{{ $space->accountsPercentageClass }}"></progress>
|
||||
@endif
|
||||
<p>
|
||||
{{ $space->accounts()->count() }}
|
||||
/
|
||||
@if ($space->max_accounts > 0){{ $space->max_accounts }} @else <i class="ph">infinity</i>@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon"><i class="ph">clock</i></span>
|
||||
<h3>Expiration</h3>
|
||||
@if ($space->isExpired())
|
||||
<p>Expired</p>
|
||||
@elseif ($space->expire_at)
|
||||
<p>In {{ $space->daysLeft }} days ({{ $space->expire_at->toDateString() }})</p>
|
||||
@else
|
||||
<p>Never expire</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-secondary oppose small" @if ($space->isFull())disabled @endif href="{{ route('admin.account.create', ['admin' => true, 'domain' => $space->domain]) }}"><i class="ph">user-plus</i> New Admin</a>
|
||||
|
||||
<h2>Admins</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@include('parts.column_sort', ['uriParams' => ['space' => $space], 'key' => 'username', 'title' => 'Identifier'])
|
||||
@include('parts.column_sort', ['uriParams' => ['space' => $space], 'key' => 'updated_at', 'title' => 'Updated'])
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($space->admins->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="4">No Admins</td>
|
||||
</tr>
|
||||
@endif
|
||||
@foreach ($space->admins as $admin)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ route('admin.account.edit', $admin->id) }}">
|
||||
{{ $admin->identifier }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ $admin->updated_at }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endsection
|
||||
14
flexiapi/resources/views/admin/space/tabs.blade.php
Normal file
14
flexiapi/resources/views/admin/space/tabs.blade.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
@php
|
||||
$items = [
|
||||
route('admin.spaces.show', $space->id) => 'Information',
|
||||
route('admin.spaces.edit', $space->id) => 'Configuration'
|
||||
];
|
||||
|
||||
if (auth()->user()->superAdmin) {
|
||||
$items[route('admin.spaces.parameters', $space->id)] = 'Parameters';
|
||||
}
|
||||
@endphp
|
||||
|
||||
@include('parts.tabs', [
|
||||
'items' => $items
|
||||
])
|
||||
|
|
@ -26,6 +26,10 @@ The endpoints are accessible using three different models:
|
|||
- <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
|
||||
|
||||
### Space expiration
|
||||
|
||||
<span class="badge badge-error">Super Admin</span> can configure and expiration date on Spaces (`expire_at`). If the Space is expired all the authenticated endpoint of the API will return `403`.
|
||||
|
||||
### Localization
|
||||
|
||||
You can add an [`Accept-Language`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) header to your request to translate the responses, and especially errors messages, in a specific language.
|
||||
|
|
@ -128,28 +132,28 @@ 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
|
||||
## Spaces
|
||||
|
||||
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>.
|
||||
Manage the list of allowed `spaces`. 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`
|
||||
### `GET /spaces`
|
||||
<span class="badge badge-error">Super Admin</span>
|
||||
|
||||
Get the list of declared SIP Domains.
|
||||
Get the list of declared Spaces.
|
||||
|
||||
### `GET /sip_domains/{domain}`
|
||||
### `GET /spaces/{domain}`
|
||||
<span class="badge badge-error">Super Admin</span>
|
||||
|
||||
Get a SIP Domain.
|
||||
Get a Space.
|
||||
|
||||
### `POST /sip_domains`
|
||||
### `POST /spaces`
|
||||
<span class="badge badge-error">Super Admin</span>
|
||||
|
||||
Create a new `sip_domain`.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `domain` **required**, the domain to use, must be unique
|
||||
* `domain` **required**, the SIP domain to use, must be unique
|
||||
* `super` **required**, boolean, set the domain as a Super Domain
|
||||
* `disable_chat_feature` boolean, disable the chat feature, default to `false`
|
||||
* `disable_meetings_feature` boolean, disable the meeting feature, default to `false`
|
||||
|
|
@ -162,8 +166,10 @@ JSON parameters:
|
|||
* `assistant_disable_qr_code` boolean, disable the QR code feature in the assistant, default to `false`
|
||||
* `assistant_hide_third_party_account` boolean, disable the call recording feature, default to `false`
|
||||
* `max_account` integer, the maximum number of accounts configurable in the app, default to `0` (infinite)
|
||||
* `max_accounts` integer, the maximum number of accounts that can be created in the space, default to `0` (infinite), cannot be less than the actual amount of accounts
|
||||
* `expire_at` date, the moment the space is expiring, default to `null` (never expire)
|
||||
|
||||
### `PUT /sip_domains/{domain}`
|
||||
### `PUT /spaces/{domain}`
|
||||
<span class="badge badge-error">Super Admin</span>
|
||||
|
||||
Update an existing `sip_domain`.
|
||||
|
|
@ -182,8 +188,10 @@ JSON parameters:
|
|||
* `assistant_disable_qr_code` **required**, boolean
|
||||
* `assistant_hide_third_party_account` **required**, boolean
|
||||
* `max_account` **required**, integer
|
||||
* `max_accounts` **required**,integer, the maximum number of accounts that can be created in the space, default to `0` (infinite), cannot be less than the actual amount of accounts
|
||||
* `expire_at` **required**, date, the moment the space is expiring, set to `null` to never expire
|
||||
|
||||
### `DELETE /sip_domains/{domain}`
|
||||
### `DELETE /spaces/{domain}`
|
||||
<span class="badge badge-error">Super Admin</span>
|
||||
|
||||
Delete a domain, **be careful, all the related accounts will also be destroyed**.
|
||||
|
|
@ -272,8 +280,11 @@ JSON parameters:
|
|||
<span class="badge badge-success">Public</span>
|
||||
|
||||
Create an account using an `account_creation_token`.
|
||||
|
||||
Return `422` if the parameters are invalid or if the token is expired.
|
||||
|
||||
Return `403` if the `max_accounts` limit of the corresponding Space is reached.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `username` unique username, minimum 6 characters
|
||||
|
|
@ -397,12 +408,14 @@ JSON parameters:
|
|||
|
||||
To create an account directly from the API. <span class="badge badge-message">Deprecated</span> If `activated` is set to `false` a random generated `confirmation_key` and `provisioning_token` will be returned to allow further activation using the public endpoints and provision the account. Check `confirmation_key_expires` to also set an expiration date on that `confirmation_key`.
|
||||
|
||||
Return `403` if the `max_accounts` limit of the corresponding Space is reached.
|
||||
|
||||
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. If the domain is not available in the `sip_domains` list, it will be created automatically.
|
||||
* `domain` **not configurable by default**, must exist in one of the configured Spaces. Only configurable if the admin is a super admin. Otherwise the SIP domain of the corresponding space is used.
|
||||
* `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`
|
||||
|
|
@ -420,7 +433,7 @@ Update an existing account. Ensure to resend all the parameters to not reset the
|
|||
JSON parameters:
|
||||
|
||||
* `username` unique username, minimum 6 characters
|
||||
* `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**, must exist in one of the configured Spaces. Only configurable if the admin is a super admin. Otherwise the SIP domain of the corresponding space is used.
|
||||
* `password` **required** minimum 6 characters
|
||||
* `algorithm` **required**, values can be `SHA-256` or `MD5`
|
||||
* `display_name` optional, string
|
||||
|
|
|
|||
5
flexiapi/resources/views/errors/402.blade.php
Normal file
5
flexiapi/resources/views/errors/402.blade.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@extends('errors::minimal')
|
||||
|
||||
@section('title', __('Payment Required'))
|
||||
@section('code', '402')
|
||||
@section('message', __('Payment Required'))
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
@section('title', __('Service Unavailable'))
|
||||
@section('code', '503')
|
||||
@section('message', __('Service Unavailable'))
|
||||
@section('message', $exception->getMessage())
|
||||
|
|
|
|||
|
|
@ -1,486 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>@yield('title')</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
html {
|
||||
line-height: 1.15;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
figcaption,
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
legend {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-webkit-box-sizing: inherit;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: #dae1e7;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input::-webkit-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
input:-ms-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: inherit;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
button,
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.bg-teal-light {
|
||||
background-color: #64d5ca;
|
||||
}
|
||||
|
||||
.bg-blue-dark {
|
||||
background-color: #2779bd;
|
||||
}
|
||||
|
||||
.bg-indigo-light {
|
||||
background-color: #7886d7;
|
||||
}
|
||||
|
||||
.bg-purple-light {
|
||||
background-color: #a779e9;
|
||||
}
|
||||
|
||||
.bg-no-repeat {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.bg-cover {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.border-grey-light {
|
||||
border-color: #dae1e7;
|
||||
}
|
||||
|
||||
.hover\:border-grey:hover {
|
||||
border-color: #b8c2cc;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.font-sans {
|
||||
font-family: Nunito, sans-serif;
|
||||
}
|
||||
|
||||
.font-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-black {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.h-1 {
|
||||
height: .25rem;
|
||||
}
|
||||
|
||||
.leading-normal {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.my-3 {
|
||||
margin-top: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.max-w-sm {
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.py-3 {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.pb-full {
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pin {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
color: #22292f;
|
||||
}
|
||||
|
||||
.text-grey-darkest {
|
||||
color: #3d4852;
|
||||
}
|
||||
|
||||
.text-grey-darker {
|
||||
color: #606f7b;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.antialiased {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.tracking-wide {
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.md\:bg-left {
|
||||
background-position: left;
|
||||
}
|
||||
|
||||
.md\:bg-right {
|
||||
background-position: right;
|
||||
}
|
||||
|
||||
.md\:flex {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.md\:my-6 {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.md\:min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.md\:pb-0 {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.md\:text-3xl {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.md\:text-15xl {
|
||||
font-size: 9rem;
|
||||
}
|
||||
|
||||
.md\:w-1\/2 {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.lg\:bg-center {
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="antialiased font-sans">
|
||||
<div class="md:flex min-h-screen">
|
||||
<div class="w-full md:w-1/2 bg-white flex items-center justify-center">
|
||||
<div class="max-w-sm m-8">
|
||||
<div class="text-black text-5xl md:text-15xl font-black">
|
||||
@yield('code', __('Oh no'))
|
||||
</div>
|
||||
|
||||
<div class="w-16 h-1 bg-purple-light my-3 md:my-6"></div>
|
||||
|
||||
<p class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal">
|
||||
@yield('message')
|
||||
</p>
|
||||
|
||||
<a href="{{ app('router')->has('home') ? route('home') : url('/') }}">
|
||||
<button class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">
|
||||
{{ __('Go Home') }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative pb-full md:flex md:pb-0 md:min-h-screen w-full md:w-1/2">
|
||||
@yield('image')
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>@yield('title')</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
html, body {
|
||||
background-color: #fff;
|
||||
color: #636b6f;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-weight: 100;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.position-ref {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex-center position-ref full-height">
|
||||
<div class="content">
|
||||
<div class="title">
|
||||
@yield('message')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<h2 class="text-center pt-5">@yield('code') - @yield('title')</h2>
|
||||
<h2>@yield('code') - @yield('title')</h2>
|
||||
|
||||
<p class="text-center">
|
||||
@yield('message')
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ asset('css/' . config('app.env') . '.style.css') }}">
|
||||
@endif
|
||||
|
||||
<script src="{{ asset('scripts/utils.js') }}""></script>
|
||||
<script src="{{ asset('scripts/utils.js') }}"></script>
|
||||
<script src="{{ asset('scripts/chart.js') }}"></script>
|
||||
<script src="{{ asset('scripts/chartjs-plugin-datalabels@2.0.0') }}"></script>
|
||||
</head>
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
@hasSection('breadcrumb')
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('account.dashboard') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('account.dashboard') }}">My Account</a></li>
|
||||
@yield('breadcrumb')
|
||||
</ol>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
@if (isset($errors) && isset($name) && count($errors->get($name)) > 0)
|
||||
@foreach ($errors->get($name) as $error)
|
||||
<small class="error">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
<div>
|
||||
@if (!isset($reverse) || !$reverse)
|
||||
<input name="{{ $key }}" value="true" type="radio" @if ($object->$key) checked @endif>
|
||||
@if (isset($reverse) && $reverse)<p>Disabled</p>@else<p>Enabled</p>@endif
|
||||
@endif
|
||||
<input name="{{ $key }}" value="false" type="radio" @if (!$object->$key) checked @endif>
|
||||
@if (isset($reverse) && $reverse)<p>Enabled</p>@else<p>Disabled</p>@endif
|
||||
<label>{{ $label }}</label>
|
||||
@if (isset($reverse) && $reverse)
|
||||
<input name="{{ $key }}" value="true" type="radio" @if ($object->$key) checked @endif>
|
||||
@if (isset($reverse) && $reverse)<p>Disabled</p>@else<p>Enabled</p>@endif
|
||||
@endif
|
||||
<div class="checkbox">
|
||||
<input id="{{ $key }}" type="checkbox" @if ($object->$key || (isset($reversed) && $reversed && !$object->$key)) checked @endif name="{{ $key }}">
|
||||
<label for="{{ $key }}"></label>
|
||||
<div>
|
||||
<p>{{ $label }}</p>
|
||||
@if (isset($supporting))
|
||||
<span class="supporting">{{ $supporting }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
<nav>
|
||||
@php
|
||||
$items = [
|
||||
'account.dashboard' => ['title' => 'Dashboard', 'icon' => 'gauge'],
|
||||
];
|
||||
$items = [];
|
||||
|
||||
if (auth()->user() && auth()->user()->admin) {
|
||||
if (auth()->user()->superAdmin) {
|
||||
$items['admin.spaces.index'] = ['title' => 'Spaces', 'icon' => 'globe-hemisphere-west'];
|
||||
$items['admin.phone_countries.index'] = ['title' => 'Phone Countries', 'icon' => 'flag'];
|
||||
} elseif (auth()->user()->admin) {
|
||||
$items['admin.spaces.me'] = ['title' => 'My Space', 'icon' => 'globe-hemisphere-west'];
|
||||
}
|
||||
|
||||
$items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'users'];
|
||||
$items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'user-rectangle'];
|
||||
$items['admin.statistics.show'] = ['title' => 'Statistics', 'icon' => 'chart-donut'];
|
||||
|
||||
if (auth()->user()->superAdmin) {
|
||||
$items['admin.sip_domains.index'] = ['title' => 'SIP Domains', 'icon' => 'hard-drivers'];
|
||||
$items['admin.phone_countries.index'] = ['title' => 'Phone Countries', 'icon' => 'flag'];
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +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\SpaceController;
|
||||
use App\Http\Controllers\Api\Admin\VcardsStorageController as AdminVcardsStorageController;
|
||||
use App\Http\Controllers\Api\StatisticsMessageController;
|
||||
use App\Http\Controllers\Api\StatisticsCallController;
|
||||
|
|
@ -61,7 +61,7 @@ Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@gen
|
|||
|
||||
Route::get('phone_countries', 'Api\PhoneCountryController@index');
|
||||
|
||||
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blocked']], function () {
|
||||
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blocked', 'space.expired']], function () {
|
||||
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
|
||||
Route::post('account_creation_tokens/consume', 'Api\Account\CreationTokenController@consume');
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
|
||||
// Super admin
|
||||
Route::group(['middleware' => ['auth.super_admin']], function () {
|
||||
Route::prefix('sip_domains')->controller(SipDomainController::class)->group(function () {
|
||||
Route::prefix('spaces')->controller(SpaceController::class)->group(function () {
|
||||
Route::get('/', 'index');
|
||||
Route::get('{domain}', 'show');
|
||||
Route::post('/', 'store');
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ use App\Http\Controllers\Admin\AccountStatisticsController;
|
|||
use App\Http\Controllers\Admin\ContactsListController;
|
||||
use App\Http\Controllers\Admin\ContactsListContactController;
|
||||
use App\Http\Controllers\Admin\PhoneCountryController;
|
||||
use App\Http\Controllers\Admin\SipDomainController;
|
||||
use App\Http\Controllers\Admin\SpaceController;
|
||||
use App\Http\Controllers\Admin\StatisticsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ Route::name('provisioning.')->prefix('provisioning')->controller(ProvisioningCon
|
|||
Route::get('/', 'show')->name('show');
|
||||
});
|
||||
|
||||
Route::group(['middleware' => 'web_panel_enabled'], function () {
|
||||
Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () {
|
||||
if (config('app.public_registration')) {
|
||||
Route::redirect('register', 'register/email')->name('account.register');
|
||||
|
||||
|
|
@ -154,10 +154,14 @@ 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::get('space', 'Admin\SpaceController@me')->name('spaces.me');
|
||||
|
||||
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::resource('spaces', SpaceController::class);
|
||||
Route::get('spaces/delete/{id}', 'Admin\SpaceController@delete')->name('spaces.delete');
|
||||
|
||||
Route::get('spaces/{space}/parameters', 'Admin\SpaceController@parameters')->name('spaces.parameters');
|
||||
Route::put('spaces/{space}/parameters', 'Admin\SpaceController@parametersUpdate')->name('spaces.parameters.update');
|
||||
|
||||
Route::name('phone_countries.')->controller(PhoneCountryController::class)->prefix('phone_countries')->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
|
|
|
|||
2
flexiapi/storage/debugbar/.gitignore
vendored
Normal file
2
flexiapi/storage/debugbar/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -23,7 +23,7 @@ use App\Account;
|
|||
use App\AuthToken;
|
||||
use App\Password;
|
||||
use App\ProvisioningToken;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ class AccountProvisioningTest extends TestCase
|
|||
|
||||
public function testUiSectionProvisioning()
|
||||
{
|
||||
$secondDomain = SipDomain::factory()->create();
|
||||
$secondDomain = Space::factory()->create();
|
||||
|
||||
$password = Password::factory()->create();
|
||||
$password->account->generateApiKey();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
namespace Tests\Feature;
|
||||
|
||||
use App\Account;
|
||||
use App\Space;
|
||||
use App\AccountCreationRequestToken;
|
||||
use App\AccountCreationToken;
|
||||
use App\Http\Middleware\ValidateJSON;
|
||||
|
|
@ -131,6 +132,7 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
public function testInvalidToken()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
Space::factory()->create();
|
||||
|
||||
// Invalid token
|
||||
$this->json($this->method, $this->accountRoute, [
|
||||
|
|
@ -165,6 +167,7 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
public function testTokenExpiration()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->expired()->create();
|
||||
Space::factory()->create();
|
||||
|
||||
config()->set('app.account_creation_token_expiration_minutes', 10);
|
||||
|
||||
|
|
@ -180,6 +183,7 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
public function testBlacklistedUsername()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
Space::factory()->create();
|
||||
|
||||
config()->set('app.account_blacklisted_usernames', 'foobar,blacklisted,username-.*');
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use App\AccountCreationToken;
|
|||
use App\AccountTombstone;
|
||||
use App\ActivationExpiration;
|
||||
use App\Password;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ class ApiAccountTest extends TestCase
|
|||
$account->generateApiKey();
|
||||
|
||||
$username = '+33612121212';
|
||||
$domain = SipDomain::first()->domain;
|
||||
$domain = Space::first()->domain;
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
|
|
@ -120,7 +120,7 @@ class ApiAccountTest extends TestCase
|
|||
$password->account->generateApiKey();
|
||||
|
||||
$username = 'blabla🔥';
|
||||
$domain = SipDomain::first()->domain;
|
||||
$domain = Space::first()->domain;
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
|
|
@ -160,7 +160,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
$password = Password::factory()->admin()->create();
|
||||
$username = 'foobar';
|
||||
$domain = SipDomain::first()->domain;
|
||||
$domain = Space::first()->domain;
|
||||
|
||||
config()->set('app.admins_manage_multi_domains', false);
|
||||
|
||||
|
|
@ -196,8 +196,8 @@ class ApiAccountTest extends TestCase
|
|||
$account->save();
|
||||
|
||||
$username = 'foobar';
|
||||
$domain1 = SipDomain::first()->domain;
|
||||
$domain2 = SipDomain::factory()->secondDomain()->create()->domain;
|
||||
$domain1 = Space::first()->domain;
|
||||
$domain2 = Space::factory()->secondDomain()->create()->domain;
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
|
|
@ -273,7 +273,7 @@ class ApiAccountTest extends TestCase
|
|||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testCreateDomainAsSuperAdmin()
|
||||
/*public function testCreateDomainAsSuperAdmin()
|
||||
{
|
||||
$superAdmin = Account::factory()->superAdmin()->create();
|
||||
$superAdmin->generateApiKey();
|
||||
|
|
@ -296,11 +296,10 @@ class ApiAccountTest extends TestCase
|
|||
'domain' => $newDomain
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('sip_domains', [
|
||||
$this->assertDatabaseHas('spaces', [
|
||||
'domain' => $newDomain
|
||||
]);
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
public function testDomainInTestDeployment()
|
||||
{
|
||||
|
|
@ -408,7 +407,7 @@ class ApiAccountTest extends TestCase
|
|||
$entryValue = 'bar';
|
||||
$entryNewKey = 'new_key';
|
||||
$entryNewValue = 'new_value';
|
||||
$domain = SipDomain::first()->domain;
|
||||
$domain = Space::first()->domain;
|
||||
|
||||
$result = $this->keyAuthenticated($admin)
|
||||
->json($this->method, $this->route, [
|
||||
|
|
@ -722,7 +721,7 @@ class ApiAccountTest extends TestCase
|
|||
$password->account->generateApiKey();
|
||||
|
||||
$username = 'username';
|
||||
$domain = SipDomain::first()->domain;
|
||||
$domain = Space::first()->domain;
|
||||
|
||||
$response = $this->generateFirstResponse($password, $this->method, $this->route);
|
||||
$this->generateSecondResponse($password, $response)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Tests\Feature;
|
|||
|
||||
use App\Account;
|
||||
use App\PhoneCountry;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiPhoneCountryTest extends TestCase
|
||||
|
|
|
|||
|
|
@ -20,24 +20,27 @@
|
|||
namespace Tests\Feature;
|
||||
|
||||
use App\Account;
|
||||
use App\SipDomain;
|
||||
use App\Space;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiSipDomainTest extends TestCase
|
||||
class ApiSpaceTest extends TestCase
|
||||
{
|
||||
protected $route = '/api/sip_domains';
|
||||
protected $method = 'POST';
|
||||
protected $route = '/api/spaces';
|
||||
protected $accountRoute = '/api/accounts';
|
||||
|
||||
public function testBaseAdmin()
|
||||
{
|
||||
$admin = Account::factory()->admin()->create();
|
||||
$admin->generateApiKey();
|
||||
|
||||
$secondDomain = SipDomain::factory()->secondDomain()->create();
|
||||
$secondDomain = Space::factory()->secondDomain()->create();
|
||||
$username = 'foo';
|
||||
|
||||
// Admin domain
|
||||
$this->keyAuthenticated($admin)
|
||||
->json('POST', '/api/accounts', [
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => $username,
|
||||
'domain' => $admin->domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
|
|
@ -47,7 +50,7 @@ class ApiSipDomainTest extends TestCase
|
|||
|
||||
// Second domain
|
||||
$this->keyAuthenticated($admin)
|
||||
->json('POST', '/api/accounts', [
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => $username,
|
||||
// The domain is ignored there, to fallback on the admin one
|
||||
'domain' => $secondDomain->domain,
|
||||
|
|
@ -57,10 +60,10 @@ class ApiSipDomainTest extends TestCase
|
|||
->assertJsonValidationErrors(['username']);
|
||||
|
||||
// Admin domain is now a super domain
|
||||
SipDomain::where('domain', $admin->domain)->update(['super' => true]);
|
||||
Space::where('domain', $admin->domain)->update(['super' => true]);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json('POST', '/api/accounts', [
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => $username,
|
||||
'domain' => $secondDomain->domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
|
|
@ -77,8 +80,9 @@ class ApiSipDomainTest extends TestCase
|
|||
$thirdDomain = 'third.domain';
|
||||
|
||||
$response = $this->keyAuthenticated($admin)
|
||||
-> json('POST', $this->route, [
|
||||
-> json($this->method, $this->route, [
|
||||
'domain' => $thirdDomain,
|
||||
'host' => $thirdDomain,
|
||||
'super' => false
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
|
@ -87,6 +91,7 @@ class ApiSipDomainTest extends TestCase
|
|||
->json('GET', $this->route)
|
||||
->assertJsonFragment([
|
||||
'domain' => $thirdDomain,
|
||||
'host' => $thirdDomain,
|
||||
'super' => false
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
|
@ -105,6 +110,7 @@ class ApiSipDomainTest extends TestCase
|
|||
->json('PUT', $this->route . '/' . $thirdDomain, $json)
|
||||
->assertJsonFragment([
|
||||
'domain' => $thirdDomain,
|
||||
'host' => $thirdDomain,
|
||||
'super' => true,
|
||||
'hide_settings' => true
|
||||
])
|
||||
|
|
@ -119,8 +125,51 @@ class ApiSipDomainTest extends TestCase
|
|||
->json('GET', $this->route)
|
||||
->assertJsonFragment([
|
||||
'domain' => $admin->domain,
|
||||
'super' => true
|
||||
'host' => $admin->domain,
|
||||
'super' => true,
|
||||
'max_accounts' => 0,
|
||||
'expire_at' => null
|
||||
])
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testUserCreation()
|
||||
{
|
||||
$admin = Account::factory()->superAdmin()->create();
|
||||
$admin->generateApiKey();
|
||||
|
||||
$domain = 'domain.com';
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => 'first',
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(403);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
-> json($this->method, $this->route, [
|
||||
'domain' => $domain,
|
||||
'host' => $domain,
|
||||
'super' => false,
|
||||
'max_accounts' => 1
|
||||
])->assertStatus(201);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => 'first',
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => 'second',
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(403);
|
||||
}
|
||||
}
|
||||
72
flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php
Normal file
72
flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?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\Space;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCaseWithSpaceMiddleware;
|
||||
|
||||
class ApiSpaceWithMiddlewareTest extends TestCaseWithSpaceMiddleware
|
||||
{
|
||||
protected $method = 'POST';
|
||||
protected $route = '/api/spaces';
|
||||
protected $accountRoute = '/api/accounts';
|
||||
|
||||
public function testExpiredSpace()
|
||||
{
|
||||
$superAdmin = Account::factory()->superAdmin()->create();
|
||||
$superAdmin->generateApiKey();
|
||||
|
||||
$username = 'username';
|
||||
|
||||
$space = Space::factory()->secondDomain()->expired()->create();
|
||||
$admin = Account::factory()->fromSpace($space)->admin()->create();
|
||||
|
||||
// Try to create a new user as an admin
|
||||
$admin->generateApiKey();
|
||||
config()->set('app.root_domain', $admin->domain);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, 'http://' . $admin->domain . $this->accountRoute, [
|
||||
'username' => 'new',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(403);
|
||||
|
||||
// Unexpire the space and try again
|
||||
$space = $this->keyAuthenticated($superAdmin)
|
||||
->get($this->route . '/' . $admin->domain)
|
||||
->json();
|
||||
|
||||
$space['expire_at'] = Carbon::tomorrow()->toDateTimeString();
|
||||
|
||||
$this->keyAuthenticated($superAdmin)
|
||||
->json('PUT', $this->route . '/' . $admin->domain, $space)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, $this->accountRoute, [
|
||||
'username' => 'new',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(200);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,8 @@
|
|||
|
||||
namespace Tests;
|
||||
|
||||
use App\Password;
|
||||
use App\Account;
|
||||
use App\PhoneCountry;
|
||||
use App\Http\Middleware\Space;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
|
|
@ -29,6 +28,7 @@ abstract class TestCase extends BaseTestCase
|
|||
{
|
||||
use CreatesApplication;
|
||||
use RefreshDatabase;
|
||||
use TestUtilsTrait;
|
||||
|
||||
protected $route = '/api/accounts/me';
|
||||
protected $method = 'GET';
|
||||
|
|
@ -37,85 +37,10 @@ abstract class TestCase extends BaseTestCase
|
|||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware([Space::class]);
|
||||
|
||||
PhoneCountry::truncate();
|
||||
PhoneCountry::factory()->france()->activated()->create();
|
||||
PhoneCountry::factory()->netherlands()->create();
|
||||
}
|
||||
|
||||
protected function disableBlockingService()
|
||||
{
|
||||
config()->set('app.blocking_amount_events_authorized_during_period', 0);
|
||||
}
|
||||
|
||||
protected function keyAuthenticated(Account $account)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'x-api-key' => $account->apiKey->key,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateFirstResponse(Password $password, ?string $method = null, ?string $route = null)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'From' => 'sip:' . $password->account->identifier
|
||||
])->json($method ?? $this->method, $route ?? $this->route);
|
||||
}
|
||||
|
||||
protected function generateSecondResponse(Password $password, $firstResponse)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'From' => 'sip:' . $password->account->identifier,
|
||||
'Authorization' => $this->generateDigest($password, $firstResponse),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateDigest(Password $password, $response, $hash = 'md5', $nc = '00000001')
|
||||
{
|
||||
$challenge = \substr($response->headers->get('www-authenticate'), 7);
|
||||
$extractedChallenge = $this->extractAuthenticateHeader($challenge);
|
||||
|
||||
$cnonce = generateNonce();
|
||||
|
||||
$a1 = $password->password;
|
||||
$a2 = hash($hash, $this->method . ':' . $this->route);
|
||||
$response = hash(
|
||||
$hash,
|
||||
sprintf(
|
||||
'%s:%s:%s:%s:%s:%s',
|
||||
$a1,
|
||||
$extractedChallenge['nonce'],
|
||||
$nc,
|
||||
$cnonce,
|
||||
$extractedChallenge['qop'],
|
||||
$a2
|
||||
)
|
||||
);
|
||||
|
||||
$digest = \sprintf(
|
||||
'username="%s",realm="%s",nonce="%s",nc=%s,cnonce="%s",uri="%s",qop=%s,response="%s",opaque="%s",algorithm=%s',
|
||||
\strstr($password->account->identifier, '@', true),
|
||||
$extractedChallenge['realm'],
|
||||
$extractedChallenge['nonce'],
|
||||
$nc,
|
||||
$cnonce,
|
||||
$this->route,
|
||||
$extractedChallenge['qop'],
|
||||
$response,
|
||||
$extractedChallenge['opaque'],
|
||||
array_flip(passwordAlgorithms())[$hash],
|
||||
);
|
||||
|
||||
return 'Digest ' . $digest;
|
||||
}
|
||||
|
||||
protected function extractAuthenticateHeader(string $string): array
|
||||
{
|
||||
preg_match_all(
|
||||
'@(realm|nonce|qop|opaque|algorithm)=[\'"]?([^\'",]+)@',
|
||||
$string,
|
||||
$array
|
||||
);
|
||||
|
||||
return array_combine($array[1], $array[2]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
flexiapi/tests/TestCaseWithSpaceMiddleware.php
Normal file
31
flexiapi/tests/TestCaseWithSpaceMiddleware.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?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;
|
||||
|
||||
use App\Http\Middleware\Space;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
abstract class TestCaseWithSpaceMiddleware extends BaseTestCase
|
||||
{
|
||||
use CreatesApplication;
|
||||
use RefreshDatabase;
|
||||
use TestUtilsTrait;
|
||||
}
|
||||
103
flexiapi/tests/TestUtilsTrait.php
Normal file
103
flexiapi/tests/TestUtilsTrait.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?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;
|
||||
|
||||
use App\Password;
|
||||
use App\Account;
|
||||
|
||||
trait TestUtilsTrait
|
||||
{
|
||||
protected function disableBlockingService()
|
||||
{
|
||||
config()->set('app.blocking_amount_events_authorized_during_period', 0);
|
||||
}
|
||||
|
||||
protected function keyAuthenticated(Account $account)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'x-api-key' => $account->apiKey->key,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateFirstResponse(Password $password, ?string $method = null, ?string $route = null)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'From' => 'sip:' . $password->account->identifier
|
||||
])->json($method ?? $this->method, $route ?? $this->route);
|
||||
}
|
||||
|
||||
protected function generateSecondResponse(Password $password, $firstResponse)
|
||||
{
|
||||
return $this->withHeaders([
|
||||
'From' => 'sip:' . $password->account->identifier,
|
||||
'Authorization' => $this->generateDigest($password, $firstResponse),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateDigest(Password $password, $response, $hash = 'md5', $nc = '00000001')
|
||||
{
|
||||
$challenge = \substr($response->headers->get('www-authenticate'), 7);
|
||||
$extractedChallenge = $this->extractAuthenticateHeader($challenge);
|
||||
|
||||
$cnonce = generateNonce();
|
||||
|
||||
$a1 = $password->password;
|
||||
$a2 = hash($hash, $this->method . ':' . $this->route);
|
||||
$response = hash(
|
||||
$hash,
|
||||
sprintf(
|
||||
'%s:%s:%s:%s:%s:%s',
|
||||
$a1,
|
||||
$extractedChallenge['nonce'],
|
||||
$nc,
|
||||
$cnonce,
|
||||
$extractedChallenge['qop'],
|
||||
$a2
|
||||
)
|
||||
);
|
||||
|
||||
$digest = \sprintf(
|
||||
'username="%s",realm="%s",nonce="%s",nc=%s,cnonce="%s",uri="%s",qop=%s,response="%s",opaque="%s",algorithm=%s',
|
||||
\strstr($password->account->identifier, '@', true),
|
||||
$extractedChallenge['realm'],
|
||||
$extractedChallenge['nonce'],
|
||||
$nc,
|
||||
$cnonce,
|
||||
$this->route,
|
||||
$extractedChallenge['qop'],
|
||||
$response,
|
||||
$extractedChallenge['opaque'],
|
||||
array_flip(passwordAlgorithms())[$hash],
|
||||
);
|
||||
|
||||
return 'Digest ' . $digest;
|
||||
}
|
||||
|
||||
protected function extractAuthenticateHeader(string $string): array
|
||||
{
|
||||
preg_match_all(
|
||||
'@(realm|nonce|qop|opaque|algorithm)=[\'"]?([^\'",]+)@',
|
||||
$string,
|
||||
$array
|
||||
);
|
||||
|
||||
return array_combine($array[1], $array[2]);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ License: GPL
|
|||
URL: http://www.linphone.org
|
||||
Source0: flexisip-account-manager.tar.gz
|
||||
|
||||
Requires: php >= 8.0, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring
|
||||
Requires: php >= 8.1, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring
|
||||
|
||||
%description
|
||||
PHP server for Linphone and Flexisip providing module for account creation.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue