From 60df61d508852f307932f8a3398507d16f394d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Tue, 26 Aug 2025 09:03:50 +0000 Subject: [PATCH] Fix FLEXIAPI-359 Add CardDav servers support in the spaces --- CHANGELOG.md | 1 + flexiapi/app/Account.php | 18 +- flexiapi/app/AccountCardDavCredentials.php | 20 + flexiapi/app/Helpers/Utils.php | 4 +- .../Account/ProvisioningController.php | 38 + .../AccountTypeController.php} | 4 +- .../ActionController.php} | 4 +- .../ActivityController.php} | 4 +- .../Account/CardDavCredentialsController.php | 104 +++ .../ContactController.php} | 4 +- .../DeviceController.php} | 4 +- .../DictionaryController.php} | 4 +- .../ImportController.php} | 4 +- .../StatisticsController.php} | 4 +- .../TypeController.php} | 4 +- .../Admin/Space/CardDavServerController.php | 68 ++ .../Controllers/Admin/SpaceController.php | 1 + .../ActionController.php} | 4 +- .../Account/CardDavCredentialsController.php | 78 ++ .../ContactController.php} | 4 +- .../CreationTokenController.php} | 4 +- .../DictionaryController.php} | 4 +- .../TypeController.php} | 4 +- .../Admin/Space/CardDavServerController.php | 56 ++ .../{ => Space}/EmailServerController.php | 2 +- .../Controllers/Api/Admin/SpaceController.php | 89 +- flexiapi/app/Http/Kernel.php | 21 +- .../app/Http/Middleware/AuthenticateAdmin.php | 2 +- .../IsCardDavCredentialsEnabled.php | 19 + .../Requests/Account/CardDavCredentials.php | 39 + .../app/Http/Requests/Space/CardDavServer.php | 42 + flexiapi/app/Rules/CommaList.php | 21 + flexiapi/app/Rules/WithoutSpaces.php | 5 - flexiapi/app/Space.php | 62 +- flexiapi/app/SpaceCardDavServer.php | 82 ++ flexiapi/config/session.php | 2 +- ...20_083715_create_space_carddav_servers.php | 62 ++ flexiapi/lang/fr.json | 13 + .../account/account_type/create.blade.php | 3 +- .../account/action/create_edit.blade.php | 3 +- .../admin/account/action/delete.blade.php | 3 +- .../admin/account/activity/index.blade.php | 3 +- .../admin/account/carddav/create.blade.php | 54 ++ .../admin/account/carddav/delete.blade.php | 31 + .../admin/account/contact/create.blade.php | 3 +- .../admin/account/contact/delete.blade.php | 3 +- .../admin/account/contact/index.blade.php | 3 +- .../views/admin/account/create_edit.blade.php | 6 +- .../views/admin/account/delete.blade.php | 3 +- .../admin/account/device/delete.blade.php | 5 +- .../account/dictionary/create_edit.blade.php | 9 +- .../admin/account/dictionary/delete.blade.php | 5 +- .../admin/account/external/delete.blade.php | 3 +- .../admin/account/external/show.blade.php | 3 +- .../admin/account/import/check.blade.php | 2 +- .../admin/account/import/create.blade.php | 2 +- .../provisioning_email/create.blade.php | 3 +- .../reset_password_email/create.blade.php | 3 +- .../views/admin/account/show.blade.php | 54 +- .../admin/account/statistics/show.blade.php | 3 +- .../statistics/show_call_logs.blade.php | 3 +- .../admin/account/type/create_edit.blade.php | 2 +- .../views/admin/account/type/delete.blade.php | 2 +- .../views/admin/account/type/index.blade.php | 2 +- .../breadcrumb/accounts/index.blade.php} | 0 .../breadcrumb/accounts/show.blade.php} | 1 + .../parts/breadcrumb/spaces/index.blade.php | 5 + .../breadcrumb/spaces/integration.blade.php | 4 + .../parts/breadcrumb/spaces/show.blade.php | 8 + .../admin/space/administration.blade.php | 15 +- .../carddav_server/create_edit.blade.php | 98 ++ .../space/carddav_server/delete.blade.php | 28 + .../views/admin/space/configuration.blade.php | 9 +- .../views/admin/space/create.blade.php | 4 +- .../views/admin/space/delete.blade.php | 4 +- .../views/admin/space/edit.blade.php | 9 +- .../admin/space/email_server/delete.blade.php | 14 +- .../admin/space/email_server/show.blade.php | 13 +- .../views/admin/space/integration.blade.php | 35 +- .../views/admin/space/show.blade.php | 4 +- .../api/documentation/about_auth.blade.php | 115 +++ .../documentation/account_tokens.blade.php | 91 ++ .../api/documentation/accounts.blade.php | 176 ++++ .../documentation/accounts/actions.blade.php | 38 + .../accounts/carddav_credentials.blade.php | 30 + .../documentation/accounts/contacts.blade.php | 22 + .../accounts/contacts_lists.blade.php | 56 ++ .../accounts/dictionary.blade.php | 15 + .../documentation/accounts/email.blade.php | 24 + .../accounts/external_account.blade.php | 26 + .../documentation/accounts/phone.blade.php | 38 + .../documentation/accounts/types.blade.php | 44 + .../accounts/vcards_storage.blade.php | 39 + .../views/api/documentation/spaces.blade.php | 95 ++ .../documentation/spaces/carddav.blade.php | 52 ++ .../api/documentation/spaces/email.blade.php | 26 + .../api/documentation/statistics.blade.php | 57 ++ .../api/documentation_markdown.blade.php | 877 +----------------- flexiapi/routes/api.php | 38 +- flexiapi/routes/web.php | 55 +- .../tests/Feature/AccountProvisioningTest.php | 53 ++ .../Feature/ApiSpaceCardDavServersTest.php | 249 +++++ .../Feature/ApiSpaceWithMiddlewareTest.php | 2 +- 103 files changed, 2453 insertions(+), 1137 deletions(-) create mode 100644 flexiapi/app/AccountCardDavCredentials.php rename flexiapi/app/Http/Controllers/Admin/{AccountAccountTypeController.php => Account/AccountTypeController.php} (96%) rename flexiapi/app/Http/Controllers/Admin/{AccountActionController.php => Account/ActionController.php} (97%) rename flexiapi/app/Http/Controllers/Admin/{AccountActivityController.php => Account/ActivityController.php} (92%) create mode 100644 flexiapi/app/Http/Controllers/Admin/Account/CardDavCredentialsController.php rename flexiapi/app/Http/Controllers/Admin/{AccountContactController.php => Account/ContactController.php} (97%) rename flexiapi/app/Http/Controllers/Admin/{AccountDeviceController.php => Account/DeviceController.php} (95%) rename flexiapi/app/Http/Controllers/Admin/{AccountDictionaryController.php => Account/DictionaryController.php} (97%) rename flexiapi/app/Http/Controllers/Admin/{AccountImportController.php => Account/ImportController.php} (99%) rename flexiapi/app/Http/Controllers/Admin/{AccountStatisticsController.php => Account/StatisticsController.php} (97%) rename flexiapi/app/Http/Controllers/Admin/{AccountTypeController.php => Account/TypeController.php} (97%) create mode 100644 flexiapi/app/Http/Controllers/Admin/Space/CardDavServerController.php rename flexiapi/app/Http/Controllers/Api/Admin/{AccountActionController.php => Account/ActionController.php} (96%) create mode 100644 flexiapi/app/Http/Controllers/Api/Admin/Account/CardDavCredentialsController.php rename flexiapi/app/Http/Controllers/Api/Admin/{AccountContactController.php => Account/ContactController.php} (95%) rename flexiapi/app/Http/Controllers/Api/Admin/{AccountCreationTokenController.php => Account/CreationTokenController.php} (93%) rename flexiapi/app/Http/Controllers/Api/Admin/{AccountDictionaryController.php => Account/DictionaryController.php} (95%) rename flexiapi/app/Http/Controllers/Api/Admin/{AccountTypeController.php => Account/TypeController.php} (95%) create mode 100644 flexiapi/app/Http/Controllers/Api/Admin/Space/CardDavServerController.php rename flexiapi/app/Http/Controllers/Api/Admin/{ => Space}/EmailServerController.php (96%) create mode 100644 flexiapi/app/Http/Middleware/IsCardDavCredentialsEnabled.php create mode 100644 flexiapi/app/Http/Requests/Account/CardDavCredentials.php create mode 100644 flexiapi/app/Http/Requests/Space/CardDavServer.php create mode 100644 flexiapi/app/Rules/CommaList.php create mode 100644 flexiapi/app/SpaceCardDavServer.php create mode 100644 flexiapi/database/migrations/2025_08_20_083715_create_space_carddav_servers.php create mode 100644 flexiapi/resources/views/admin/account/carddav/create.blade.php create mode 100644 flexiapi/resources/views/admin/account/carddav/delete.blade.php rename flexiapi/resources/views/admin/{account/parts/breadcrumb_accounts_index.blade.php => parts/breadcrumb/accounts/index.blade.php} (100%) rename flexiapi/resources/views/admin/{account/parts/breadcrumb_accounts_show.blade.php => parts/breadcrumb/accounts/show.blade.php} (68%) create mode 100644 flexiapi/resources/views/admin/parts/breadcrumb/spaces/index.blade.php create mode 100644 flexiapi/resources/views/admin/parts/breadcrumb/spaces/integration.blade.php create mode 100644 flexiapi/resources/views/admin/parts/breadcrumb/spaces/show.blade.php create mode 100644 flexiapi/resources/views/admin/space/carddav_server/create_edit.blade.php create mode 100644 flexiapi/resources/views/admin/space/carddav_server/delete.blade.php create mode 100644 flexiapi/resources/views/api/documentation/about_auth.blade.php create mode 100644 flexiapi/resources/views/api/documentation/account_tokens.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/actions.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/carddav_credentials.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/contacts.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/contacts_lists.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/dictionary.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/email.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/external_account.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/phone.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/types.blade.php create mode 100644 flexiapi/resources/views/api/documentation/accounts/vcards_storage.blade.php create mode 100644 flexiapi/resources/views/api/documentation/spaces.blade.php create mode 100644 flexiapi/resources/views/api/documentation/spaces/carddav.blade.php create mode 100644 flexiapi/resources/views/api/documentation/spaces/email.blade.php create mode 100644 flexiapi/resources/views/api/documentation/statistics.blade.php create mode 100644 flexiapi/tests/Feature/ApiSpaceCardDavServersTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c571216..90714c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ v2.1 ---- - Fix FLEXIAPI-282 Migrate to Laravel 11 and PHP 8.2+ - Fix FLEXIAPI-371 Add documentation for the Wizard page +- Fix FLEXIAPI-359 Add CardDav servers support in the spaces v2.0 ---- diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 3427ca8..e63c5c9 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -25,6 +25,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Http\Request; use Illuminate\Support\Str; +use Illuminate\Database\Eloquent\Collection; use Carbon\Carbon; use Awobaz\Compoships\Compoships; @@ -36,7 +37,7 @@ class Account extends Authenticatable use HasFactory; use Compoships; - protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries']; + protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries', 'carddavServers']; protected $hidden = ['expire_time', 'pivot', 'currentProvisioningToken', 'currentRecoveryCode', 'dictionaryEntries']; protected $appends = ['realm', 'provisioning_token', 'provisioning_token_expire_at', 'dictionary']; protected $casts = [ @@ -149,6 +150,12 @@ class Account extends Authenticatable return $this->hasMany(AccountDictionaryEntry::class); } + public function carddavServers() + { + return $this->belongsToMany(SpaceCardDavServer::class, 'account_carddav_credentials', 'account_id', 'space_carddav_server_id') + ->withPivot('username', 'domain', 'algorithm', 'password'); + } + public function getDictionaryAttribute() { if ($this->dictionaryEntries->isEmpty()) return new stdClass; @@ -309,6 +316,15 @@ class Account extends Authenticatable return null; } + public function getRemainingCardDavCredentialsCreatableAttribute(): Collection + { + return $this->space->carddavServers()->whereNotIn('id', function ($query) { + $query->select('space_carddav_server_id') + ->from('account_carddav_credentials') + ->where('account_id', $this->id); + })->get(); + } + public function getIdentifierAttribute(): string { return $this->attributes['username'] . '@' . $this->attributes['domain']; diff --git a/flexiapi/app/AccountCardDavCredentials.php b/flexiapi/app/AccountCardDavCredentials.php new file mode 100644 index 0000000..92846fc --- /dev/null +++ b/flexiapi/app/AccountCardDavCredentials.php @@ -0,0 +1,20 @@ +hasOne(SpaceCardDavServer::class, 'id', 'space_carddav_server_id'); + } + + public function getIdentifierAttribute() + { + return $this->username . '@' . $this->domain; + } +} diff --git a/flexiapi/app/Helpers/Utils.php b/flexiapi/app/Helpers/Utils.php index 443f81b..f61d0a8 100644 --- a/flexiapi/app/Helpers/Utils.php +++ b/flexiapi/app/Helpers/Utils.php @@ -28,11 +28,9 @@ use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension; use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension; use Illuminate\Support\Facades\DB; -$hostSpace = null; - function space(): ?Space { - return request()->space; + return is_object(request()->space) ? request()->space :null; } function passwordAlgorithms(): array diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index 059dd46..b1cd4c3 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -189,6 +189,44 @@ class ProvisioningController extends Controller } } + $remoteContactDirectoryCounter = 0; + $authInfoIndex = 0; + + // CardDav servers + + if ($request->space?->carddavServers) { + foreach ($request->space->carddavServers as $carddavServer) { + $carddavServer->getProvisioningSection($config, $remoteContactDirectoryCounter); + $remoteContactDirectoryCounter++; + } + } + + if ($account) { + foreach ($account->carddavServers as $carddavServer) { + $section = $dom->createElement('section'); + $section->setAttribute('name', 'auth_info_' . $authInfoIndex); + $config->appendChild($section); + + $entry = $dom->createElement('entry', $carddavServer->pivot->username); + $entry->setAttribute('name', 'username'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $carddavServer->pivot->domain); + $entry->setAttribute('name', 'domain'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $carddavServer->pivot->password); + $entry->setAttribute('name', 'ha1'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $carddavServer->pivot->algorithm); + $entry->setAttribute('name', 'algorithm'); + $section->appendChild($entry); + + $authInfoIndex++; + } + } + // Password reset if ($account && $request->has('reset_password')) { $account->updatePassword(Str::random(10)); diff --git a/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php b/flexiapi/app/Http/Controllers/Admin/Account/AccountTypeController.php similarity index 96% rename from flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php rename to flexiapi/app/Http/Controllers/Admin/Account/AccountTypeController.php index 110569e..817568b 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/AccountTypeController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -26,7 +26,7 @@ use Illuminate\Support\Facades\Log; use App\Account; use App\AccountType; -class AccountAccountTypeController extends Controller +class AccountTypeController extends Controller { public function create(int $id) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountActionController.php b/flexiapi/app/Http/Controllers/Admin/Account/ActionController.php similarity index 97% rename from flexiapi/app/Http/Controllers/Admin/AccountActionController.php rename to flexiapi/app/Http/Controllers/Admin/Account/ActionController.php index c3d1d1f..0419600 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountActionController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/ActionController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -27,7 +27,7 @@ use App\Account; use App\AccountAction; use App\Rules\NoUppercase; -class AccountActionController extends Controller +class ActionController extends Controller { public function create(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountActivityController.php b/flexiapi/app/Http/Controllers/Admin/Account/ActivityController.php similarity index 92% rename from flexiapi/app/Http/Controllers/Admin/AccountActivityController.php rename to flexiapi/app/Http/Controllers/Admin/Account/ActivityController.php index e4ffefb..d3882fa 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountActivityController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/ActivityController.php @@ -17,13 +17,13 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use App\Account; -class AccountActivityController extends Controller +class ActivityController extends Controller { public function index(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/Account/CardDavCredentialsController.php b/flexiapi/app/Http/Controllers/Admin/Account/CardDavCredentialsController.php new file mode 100644 index 0000000..a551c04 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/Account/CardDavCredentialsController.php @@ -0,0 +1,104 @@ +. +*/ + +namespace App\Http\Controllers\Admin\Account; + +use App\Account; +use App\AccountCardDavCredentials; +use App\Http\Controllers\Controller; +use Illuminate\Database\Query\Builder; +use Illuminate\Http\Request; +use Illuminate\Validation\Rule; +use App\Http\Requests\Account\CardDavCredentials; + +class CardDavCredentialsController extends Controller +{ + public function create(int $accountId) + { + $account = Account::findOrFail($accountId); + $this->checkFeatureEnabled($account); + + return view('admin.account.carddav.create', [ + 'account' => $account, + 'carddavServers' => $account->remainingCardDavCredentialsCreatable + ]); + } + + public function store(CardDavCredentials $request, int $accountId) + { + $account = Account::findOrFail($accountId); + $this->checkFeatureEnabled($account); + + $request->validate([ + 'carddav_id' => ['required', Rule::exists('space_carddav_servers', 'id')->where(function (Builder $query) use ($account) { + return $query->where('space_id', $account->space->id); + })] + ]); + + $accountCarddavCredentials = new AccountCardDavCredentials; + $accountCarddavCredentials->space_carddav_server_id = $request->get('carddav_id'); + $accountCarddavCredentials->account_id = $account->id; + $accountCarddavCredentials->username = $request->get('username'); + $accountCarddavCredentials->domain = $request->get('domain'); + $accountCarddavCredentials->password = bchash( + $request->get('username'), + $request->get('domain'), + $request->get('password'), + $request->get('algorithm') + ); + $accountCarddavCredentials->algorithm = $request->get('algorithm'); + $accountCarddavCredentials->save(); + + return redirect()->route('admin.account.show', $account); + } + + public function delete(int $accountId, int $cardDavId) + { + $account = Account::findOrFail($accountId); + $this->checkFeatureEnabled($account); + + $accountCarddavCredentials = AccountCardDavCredentials::where('space_carddav_server_id', $cardDavId) + ->where('account_id', $account->id) + ->firstOrFail(); + + return view('admin.account.carddav.delete', [ + 'account' => $account, + 'carddavCredentials' => $accountCarddavCredentials, + ]); + } + + public function destroy(Request $request, int $accountId) + { + $account = Account::findOrFail($accountId); + $this->checkFeatureEnabled($account); + + $accountCarddavCredentials = AccountCardDavCredentials::where('space_carddav_server_id', $request->carddav_id) + ->where('account_id', $account->id) + ->delete(); + + return redirect()->route('admin.account.show', $account); + } + + private function checkFeatureEnabled(Account $account) + { + if (!$account->space->carddav_user_credentials) { + abort(403, 'CardDav Credentials features disabled'); + } + } +} diff --git a/flexiapi/app/Http/Controllers/Admin/AccountContactController.php b/flexiapi/app/Http/Controllers/Admin/Account/ContactController.php similarity index 97% rename from flexiapi/app/Http/Controllers/Admin/AccountContactController.php rename to flexiapi/app/Http/Controllers/Admin/Account/ContactController.php index ff8c46e..0f07173 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountContactController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/ContactController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -26,7 +26,7 @@ use Illuminate\Support\Facades\Log; use App\Account; use App\ContactsList; -class AccountContactController extends Controller +class ContactController extends Controller { public function index(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountDeviceController.php b/flexiapi/app/Http/Controllers/Admin/Account/DeviceController.php similarity index 95% rename from flexiapi/app/Http/Controllers/Admin/AccountDeviceController.php rename to flexiapi/app/Http/Controllers/Admin/Account/DeviceController.php index df76ecf..80d4775 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountDeviceController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/DeviceController.php @@ -17,14 +17,14 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\Libraries\FlexisipRedisConnector; -class AccountDeviceController extends Controller +class DeviceController extends Controller { public function index(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountDictionaryController.php b/flexiapi/app/Http/Controllers/Admin/Account/DictionaryController.php similarity index 97% rename from flexiapi/app/Http/Controllers/Admin/AccountDictionaryController.php rename to flexiapi/app/Http/Controllers/Admin/Account/DictionaryController.php index 4118fcf..b32a1f3 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountDictionaryController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/DictionaryController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -25,7 +25,7 @@ use Illuminate\Http\Request; use App\Account; use App\AccountDictionaryEntry; -class AccountDictionaryController extends Controller +class DictionaryController extends Controller { public function create(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountImportController.php b/flexiapi/app/Http/Controllers/Admin/Account/ImportController.php similarity index 99% rename from flexiapi/app/Http/Controllers/Admin/AccountImportController.php rename to flexiapi/app/Http/Controllers/Admin/Account/ImportController.php index 46d8406..931b379 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountImportController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/ImportController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Account; use App\ExternalAccount; @@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Validation\Rules\File; use Propaganistas\LaravelPhone\PhoneNumber; -class AccountImportController extends Controller +class ImportController extends Controller { private Collection $errors; private string $importDirectory = 'imported_csv'; diff --git a/flexiapi/app/Http/Controllers/Admin/AccountStatisticsController.php b/flexiapi/app/Http/Controllers/Admin/Account/StatisticsController.php similarity index 97% rename from flexiapi/app/Http/Controllers/Admin/AccountStatisticsController.php rename to flexiapi/app/Http/Controllers/Admin/Account/StatisticsController.php index 98d6808..df0e58c 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountStatisticsController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/StatisticsController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Account; use App\Http\Controllers\Controller; @@ -26,7 +26,7 @@ use App\StatisticsCall; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -class AccountStatisticsController extends Controller +class StatisticsController extends Controller { public function edit(Request $request, int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountTypeController.php b/flexiapi/app/Http/Controllers/Admin/Account/TypeController.php similarity index 97% rename from flexiapi/app/Http/Controllers/Admin/AccountTypeController.php rename to flexiapi/app/Http/Controllers/Admin/Account/TypeController.php index 8e5a2b9..01ec834 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountTypeController.php +++ b/flexiapi/app/Http/Controllers/Admin/Account/TypeController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Admin; +namespace App\Http\Controllers\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -27,7 +27,7 @@ use Illuminate\Validation\Rule; use App\AccountType; use App\Rules\NoUppercase; -class AccountTypeController extends Controller +class TypeController extends Controller { public function index() { diff --git a/flexiapi/app/Http/Controllers/Admin/Space/CardDavServerController.php b/flexiapi/app/Http/Controllers/Admin/Space/CardDavServerController.php new file mode 100644 index 0000000..26257b8 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/Space/CardDavServerController.php @@ -0,0 +1,68 @@ + $space, + 'carddavServer' => new SpaceCardDavServer + ]); + } + + public function store(CardDavServer $request) + { + $carddavServer = new SpaceCardDavServer; + $carddavServer->space_id = $request->space->id; + $carddavServer->fill($request->validated()); + $carddavServer->enabled = getRequestBoolean($request, 'enabled'); + $carddavServer->use_exact_match_policy = getRequestBoolean($request, 'use_exact_match_policy'); + $carddavServer->save(); + + return redirect()->route('admin.spaces.integration', $request->space); + } + + public function edit(Space $space, int $carddavServerId) + { + return view('admin.space.carddav_server.create_edit', [ + 'space' => $space, + 'carddavServer' => $space->carddavServers()->findOrFail($carddavServerId) + ]); + } + + public function update(CardDavServer $request, Space $space, int $carddavServerId) + { + $carddavServer = $space->carddavServers()->findOrFail($carddavServerId); + $carddavServer->fill($request->validated()); + $carddavServer->enabled = getRequestBoolean($request, 'enabled'); + $carddavServer->use_exact_match_policy = getRequestBoolean($request, 'use_exact_match_policy'); + $carddavServer->save(); + + return redirect()->route('admin.spaces.integration', $request->space); + } + + public function delete(Space $space, int $carddavServerId) + { + return view('admin.space.carddav_server.delete', [ + 'space' => $space, + 'carddavServer' => $space->carddavServers()->findOrFail($carddavServerId) + ]); + } + + public function destroy(Space $space, int $carddavServerId) + { + $carddavServer = $space->carddavServers()->findOrFail($carddavServerId); + $carddavServer->delete(); + + return redirect()->route('admin.spaces.integration', $space->id); + } +} diff --git a/flexiapi/app/Http/Controllers/Admin/SpaceController.php b/flexiapi/app/Http/Controllers/Admin/SpaceController.php index ddf2b85..9ec7e7c 100644 --- a/flexiapi/app/Http/Controllers/Admin/SpaceController.php +++ b/flexiapi/app/Http/Controllers/Admin/SpaceController.php @@ -146,6 +146,7 @@ class SpaceController extends Controller $space->expire_at = $request->get('expire_at'); $space->custom_theme = getRequestBoolean($request, 'custom_theme'); $space->web_panel = getRequestBoolean($request, 'web_panel'); + $space->carddav_user_credentials = getRequestBoolean($request, 'carddav_user_credentials'); $space->save(); return redirect()->route('admin.spaces.show', $space); diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountActionController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/ActionController.php similarity index 96% rename from flexiapi/app/Http/Controllers/Api/Admin/AccountActionController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Account/ActionController.php index 154ddb5..1b7159d 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountActionController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/ActionController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Api\Admin; +namespace App\Http\Controllers\Api\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -26,7 +26,7 @@ use App\Account; use App\AccountAction; use App\Rules\NoUppercase; -class AccountActionController extends Controller +class ActionController extends Controller { public function index(int $id) { diff --git a/flexiapi/app/Http/Controllers/Api/Admin/Account/CardDavCredentialsController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/CardDavCredentialsController.php new file mode 100644 index 0000000..3393672 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/CardDavCredentialsController.php @@ -0,0 +1,78 @@ +space->accounts()->findOrFail($accountId); + $cardDavServers = $account->carddavServers; + + if ($cardDavServers->isEmpty()) return new \stdClass; + + return $cardDavServers->map(function ($cardDavServer) { + return $this->extractCardDavServer($cardDavServer); + })->keyBy('carddav_id'); + } + + public function show(Request $request, int $accountId, int $cardDavServerId) + { + $account = $request->space->accounts()->findOrFail($accountId); + $cardDavServer = $account->cardDavServers()->findOrFail($cardDavServerId); + + return $this->extractCardDavServer($cardDavServer); + } + + public function update(CardDavCredentials $request, int $accountId, int $cardDavServerId) + { + $account = $request->space->accounts()->findOrFail($accountId); + $cardDavServer = $request->space->cardDavServers()->findOrFail($cardDavServerId); + + $accountCarddavCredentials = AccountCardDavCredentials::where('account_id', $account->id) + ->where('space_carddav_server_id', $cardDavServer->id) + ->delete(); + + $accountCarddavCredentials = new AccountCardDavCredentials; + $accountCarddavCredentials->space_carddav_server_id = $cardDavServer->id; + $accountCarddavCredentials->account_id = $account->id; + $accountCarddavCredentials->username = $request->get('username'); + $accountCarddavCredentials->domain = $request->get('domain'); + $accountCarddavCredentials->password = bchash( + $request->get('username'), + $request->get('domain'), + $request->get('password'), + $request->get('algorithm') + ); + $accountCarddavCredentials->algorithm = $request->get('algorithm'); + return $accountCarddavCredentials->save(); + } + + public function destroy(Request $request, int $accountId, int $cardDavServerId) + { + $account = $request->space->accounts()->findOrFail($accountId); + $cardDavServer = $account->cardDavServers()->findOrFail($cardDavServerId); + + return $cardDavServer->delete(); + } + + private function extractCardDavServer(SpaceCardDavServer $cardDavServer) + { + return [ + 'carddav_id' => $cardDavServer->id, + 'username' => $cardDavServer->pivot->username, + 'domain' => $cardDavServer->pivot->domain, + 'algorithm' => $cardDavServer->pivot->algorithm, + 'password' => $cardDavServer->pivot->password, + ]; + } +} diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountContactController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/ContactController.php similarity index 95% rename from flexiapi/app/Http/Controllers/Api/Admin/AccountContactController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Account/ContactController.php index 7217efc..022d8f3 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountContactController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/ContactController.php @@ -17,13 +17,13 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Api\Admin; +namespace App\Http\Controllers\Api\Admin\Account; use App\Http\Controllers\Controller; use App\Account; -class AccountContactController extends Controller +class ContactController extends Controller { public function index(int $id) { diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountCreationTokenController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/CreationTokenController.php similarity index 93% rename from flexiapi/app/Http/Controllers/Api/Admin/AccountCreationTokenController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Account/CreationTokenController.php index 477e354..3e5f2ef 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountCreationTokenController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/CreationTokenController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Api\Admin; +namespace App\Http\Controllers\Api\Admin\Account; use App\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -26,7 +26,7 @@ use Illuminate\Support\Str; use App\AccountCreationToken; use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; -class AccountCreationTokenController extends Controller +class CreationTokenController extends Controller { public function create(Request $request) { diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountDictionaryController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/DictionaryController.php similarity index 95% rename from flexiapi/app/Http/Controllers/Api/Admin/AccountDictionaryController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Account/DictionaryController.php index f5671f1..b4a1275 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountDictionaryController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/DictionaryController.php @@ -17,14 +17,14 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Api\Admin; +namespace App\Http\Controllers\Api\Admin\Account; use App\Http\Controllers\Controller; use App\Account; use Illuminate\Http\Request; -class AccountDictionaryController extends Controller +class DictionaryController extends Controller { public function index(int $accountId) { diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountTypeController.php b/flexiapi/app/Http/Controllers/Api/Admin/Account/TypeController.php similarity index 95% rename from flexiapi/app/Http/Controllers/Api/Admin/AccountTypeController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Account/TypeController.php index 59b6676..e9f25b2 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountTypeController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Account/TypeController.php @@ -17,7 +17,7 @@ along with this program. If not, see . */ -namespace App\Http\Controllers\Api\Admin; +namespace App\Http\Controllers\Api\Admin\Account; use App\Http\Controllers\Controller; use App\Rules\NoUppercase; @@ -25,7 +25,7 @@ use Illuminate\Http\Request; use App\AccountType; -class AccountTypeController extends Controller +class TypeController extends Controller { public function index() { diff --git a/flexiapi/app/Http/Controllers/Api/Admin/Space/CardDavServerController.php b/flexiapi/app/Http/Controllers/Api/Admin/Space/CardDavServerController.php new file mode 100644 index 0000000..9f596d3 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Api/Admin/Space/CardDavServerController.php @@ -0,0 +1,56 @@ +firstOrFail()->carddavServers; + } + + public function show(string $host, int $carddavServerId) + { + return Space::where('host', $host)->firstOrFail()->carddavServers()->findOrFail($carddavServerId); + } + + public function store(CardDavServer $request, string $host) + { + $space = Space::where('host', $host)->firstOrFail(); + + $carddavServer = new SpaceCardDavServer; + $carddavServer->space_id = $space->id; + $carddavServer->fill($request->validated()); + $carddavServer->enabled = $request->has('enabled') && (bool)$request->get('enabled'); + $carddavServer->use_exact_match_policy = $request->has('use_exact_match_policy') && (bool)$request->get('use_exact_match_policy'); + + return $carddavServer->save(); + } + + public function update(CardDavServer $request, string $host, int $carddavServerId) + { + $space = Space::where('host', $host)->firstOrFail(); + + $carddavServer = $space->carddavServers()->findOrFail($carddavServerId); + $carddavServer->fill($request->validated()); + $carddavServer->enabled = $request->has('enabled') && (bool)$request->get('enabled'); + $carddavServer->use_exact_match_policy = $request->has('use_exact_match_policy') && (bool)$request->get('use_exact_match_policy'); + + return $carddavServer->save(); + } + + public function destroy(string $host, int $carddavServerId) + { + $space = Space::where('host', $host)->firstOrFail(); + + $carddavServer = $space->carddavServers()->findOrFail($carddavServerId); + return $carddavServer->delete(); + } +} diff --git a/flexiapi/app/Http/Controllers/Api/Admin/EmailServerController.php b/flexiapi/app/Http/Controllers/Api/Admin/Space/EmailServerController.php similarity index 96% rename from flexiapi/app/Http/Controllers/Api/Admin/EmailServerController.php rename to flexiapi/app/Http/Controllers/Api/Admin/Space/EmailServerController.php index 11f9f2f..5e4298f 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/EmailServerController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/Space/EmailServerController.php @@ -1,6 +1,6 @@ name = $request->get('name'); - $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->copyright_text = $request->get('copyright_text'); - $space->intro_registration_text = $request->get('intro_registration_text'); - $space->newsletter_registration_address = $request->get('newsletter_registration_address'); $space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address'); $space->account_realm = $request->get('account_realm'); + $space->copyright_text = $request->get('copyright_text'); $space->custom_provisioning_entries = $request->get('custom_provisioning_entries'); + $space->domain = $request->get('domain'); + $space->expire_at = $request->get('expire_at'); + $space->host = $request->get('host'); + $space->intro_registration_text = $request->get('intro_registration_text'); + $space->max_account = $request->get('max_account', 0); + $space->max_accounts = $request->get('max_accounts', 0); + $space->name = $request->get('name'); + $space->newsletter_registration_address = $request->get('newsletter_registration_address'); + $this->setRequestBoolean($request, $space, 'assistant_disable_qr_code'); + $this->setRequestBoolean($request, $space, 'assistant_hide_create_account'); + $this->setRequestBoolean($request, $space, 'assistant_hide_third_party_account'); + $this->setRequestBoolean($request, $space, 'carddav_user_credentials'); $this->setRequestBoolean($request, $space, 'custom_provisioning_overwrite_all'); - $this->setRequestBoolean($request, $space, 'provisioning_use_linphone_provisioning_header'); $this->setRequestBoolean($request, $space, 'custom_theme'); - $this->setRequestBoolean($request, $space, 'web_panel'); - $this->setRequestBoolean($request, $space, 'public_registration'); - $this->setRequestBoolean($request, $space, 'phone_registration'); + $this->setRequestBoolean($request, $space, 'disable_broadcast_feature'); + $this->setRequestBoolean($request, $space, 'disable_call_recordings_feature'); + $this->setRequestBoolean($request, $space, 'disable_chat_feature'); + $this->setRequestBoolean($request, $space, 'disable_meetings_feature'); + $this->setRequestBoolean($request, $space, 'hide_account_settings'); + $this->setRequestBoolean($request, $space, 'hide_settings'); $this->setRequestBoolean($request, $space, 'intercom_features'); - + $this->setRequestBoolean($request, $space, 'only_display_sip_uri_username'); + $this->setRequestBoolean($request, $space, 'phone_registration'); + $this->setRequestBoolean($request, $space, 'provisioning_use_linphone_provisioning_header'); + $this->setRequestBoolean($request, $space, 'public_registration'); + $this->setRequestBoolean($request, $space, 'super'); + $this->setRequestBoolean($request, $space, 'web_panel'); $space->save(); return $space->refresh(); @@ -90,30 +89,30 @@ class SpaceController extends Controller 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', 'account_realm' => ['nullable', new Domain()], - + 'assistant_disable_qr_code' => 'required|boolean', + 'assistant_hide_create_account' => 'required|boolean', + 'assistant_hide_third_party_account' => 'required|boolean', + 'carddav_user_credentials' => 'required|boolean', 'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)], 'custom_provisioning_overwrite_all' => 'required|boolean', - 'provisioning_use_linphone_provisioning_header' => 'required|boolean', 'custom_theme' => 'required|boolean', - 'web_panel' => 'required|boolean', - 'public_registration' => 'required|boolean', - 'phone_registration' => 'required|boolean', + 'disable_broadcast_feature' => 'required|boolean', + 'disable_call_recordings_feature' => 'required|boolean', + 'disable_chat_feature' => 'required|boolean', + 'disable_meetings_feature' => 'required|boolean', + 'expire_at' => 'nullable|date|after_or_equal:today', + 'hide_account_settings' => 'required|boolean', + 'hide_settings' => 'required|boolean', 'intercom_features' => 'required|boolean', + 'max_account' => 'required|integer', + 'max_accounts' => 'required|integer', + 'only_display_sip_uri_username' => 'required|boolean', + 'phone_registration' => 'required|boolean', + 'provisioning_use_linphone_provisioning_header' => 'required|boolean', + 'public_registration' => 'required|boolean', + 'super' => 'required|boolean', + 'web_panel' => 'required|boolean', ]); $space = Space::where('domain', $domain)->firstOrFail(); diff --git a/flexiapi/app/Http/Kernel.php b/flexiapi/app/Http/Kernel.php index cf9cab1..4b9a831 100644 --- a/flexiapi/app/Http/Kernel.php +++ b/flexiapi/app/Http/Kernel.php @@ -72,30 +72,31 @@ class Kernel extends HttpKernel * @var array */ protected $middlewareAliases = [ - 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class, - 'auth.super_admin' => \App\Http\Middleware\AuthenticateSuperAdmin::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.check_blocked' => \App\Http\Middleware\CheckBlocked::class, 'auth.digest_or_key' => \App\Http\Middleware\AuthenticateDigestOrKey::class, 'auth.jwt' => \App\Http\Middleware\AuthenticateJWT::class, - 'auth.check_blocked' => \App\Http\Middleware\CheckBlocked::class, - 'validate_json' => \App\Http\Middleware\ValidateJSON::class, - 'web_panel_enabled' => \App\Http\Middleware\IsWebPanelEnabled::class, - 'public_registration' => \App\Http\Middleware\IsPublicRegistration::class, - 'phone_registration' => \App\Http\Middleware\IsPhoneRegistration::class, - 'intercom_features' => \App\Http\Middleware\IsIntercomFeatures::class, + 'auth.super_admin' => \App\Http\Middleware\AuthenticateSuperAdmin::class, + 'auth' => \App\Http\Middleware\Authenticate::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'cookie' => \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 'cookie.encrypt' => \App\Http\Middleware\EncryptCookies::class, + 'cookie' => \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + 'feature.carddav_user_credentials' => \App\Http\Middleware\IsCardDavCredentialsEnabled::class, + 'feature.intercom' => \App\Http\Middleware\IsIntercomFeatures::class, + 'feature.phone_registration' => \App\Http\Middleware\IsPhoneRegistration::class, + 'feature.public_registration' => \App\Http\Middleware\IsPublicRegistration::class, + 'feature.web_panel_enabled' => \App\Http\Middleware\IsWebPanelEnabled::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'localization' => \App\Http\Middleware\Localization::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'space.check' => \App\Http\Middleware\SpaceCheck::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'validate_json' => \App\Http\Middleware\ValidateJSON::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'localization' => \App\Http\Middleware\Localization::class, ]; /** diff --git a/flexiapi/app/Http/Middleware/AuthenticateAdmin.php b/flexiapi/app/Http/Middleware/AuthenticateAdmin.php index 83133de..ca94aa1 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateAdmin.php +++ b/flexiapi/app/Http/Middleware/AuthenticateAdmin.php @@ -29,7 +29,7 @@ class AuthenticateAdmin return redirect()->route('account.login'); } - if (!$request->user()->admin) { + if (!$request->user()->admin || $request->user()->domain != $request->space->domain) { return abort(403, 'Unauthorized area'); } diff --git a/flexiapi/app/Http/Middleware/IsCardDavCredentialsEnabled.php b/flexiapi/app/Http/Middleware/IsCardDavCredentialsEnabled.php new file mode 100644 index 0000000..b62c102 --- /dev/null +++ b/flexiapi/app/Http/Middleware/IsCardDavCredentialsEnabled.php @@ -0,0 +1,19 @@ +space->carddav_user_credentials) { + return $next($request); + } + + return abort(403, 'CardDav Credentials features disabled'); + } +} diff --git a/flexiapi/app/Http/Requests/Account/CardDavCredentials.php b/flexiapi/app/Http/Requests/Account/CardDavCredentials.php new file mode 100644 index 0000000..403e8de --- /dev/null +++ b/flexiapi/app/Http/Requests/Account/CardDavCredentials.php @@ -0,0 +1,39 @@ +. +*/ + +namespace App\Http\Requests\Account; + +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; + +use App\Rules\Domain; +use App\Rules\PasswordAlgorithm; + +class CardDavCredentials extends FormRequest +{ + public function rules() + { + return [ + 'username' => 'required|min:3', + 'password' => 'required', + 'algorithm' => ['required', new PasswordAlgorithm], + 'domain' => ['required', new Domain], + ]; + } +} diff --git a/flexiapi/app/Http/Requests/Space/CardDavServer.php b/flexiapi/app/Http/Requests/Space/CardDavServer.php new file mode 100644 index 0000000..16c95c5 --- /dev/null +++ b/flexiapi/app/Http/Requests/Space/CardDavServer.php @@ -0,0 +1,42 @@ +. +*/ + +namespace App\Http\Requests\Space; + +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; + +use App\EmailServer; +use App\Rules\CommaList; + +class CardDavServer extends FormRequest +{ + public function rules() + { + return [ + 'uri' => 'required|url', + 'min_characters' => 'integer|min:1', + 'results_limit' => 'integer|min:0', + 'timeout' => 'integer|min:1', + 'delay' => 'integer|min:100', + 'fields_for_user_input' => [new CommaList], + 'fields_for_domain' => [new CommaList], + ]; + } +} diff --git a/flexiapi/app/Rules/CommaList.php b/flexiapi/app/Rules/CommaList.php new file mode 100644 index 0000000..e01a0c5 --- /dev/null +++ b/flexiapi/app/Rules/CommaList.php @@ -0,0 +1,21 @@ + 'boolean', + 'assistant_disable_qr_code' => 'boolean', + 'assistant_hide_create_account' => 'boolean', + 'assistant_hide_third_party_account' => 'boolean', + 'carddav_user_credentials' => 'boolean', + 'disable_broadcast_feature' => 'boolean', + 'disable_call_recordings_feature' => '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', + 'hide_account_settings' => 'boolean', + 'hide_settings' => 'boolean', + 'only_display_sip_uri_username' => 'boolean', + 'super' => 'boolean', ]; public const HOST_REGEX = '[\w\-]+'; public const DOMAIN_REGEX = '(?=^.{4,253}$)(^((?!-)[a-z0-9-]{1,63}(?superAdmin) { + return; + } + + $builder->where('domain', Auth::user()->domain); + }); + } + public function accounts() { return $this->hasMany(Account::class, 'domain', 'domain'); @@ -82,6 +97,11 @@ class Space extends Model return $this->hasOne(SpaceEmailServer::class); } + public function carddavServers() + { + return $this->hasMany(SpaceCardDavServer::class); + } + public function scopeNotFull(Builder $query) { return $query->where('max_accounts', 0) diff --git a/flexiapi/app/SpaceCardDavServer.php b/flexiapi/app/SpaceCardDavServer.php new file mode 100644 index 0000000..ef4fd2a --- /dev/null +++ b/flexiapi/app/SpaceCardDavServer.php @@ -0,0 +1,82 @@ + 'boolean', + 'use_exact_match_policy' => 'boolean', + ]; + + public function space() + { + return $this->belongsTo(Space::class); + } + + public function accounts() + { + return $this->belongsToMany(Account::class, 'account_carddav_credentials', 'space_carddav_server_id', 'account_id'); + } + + public function getNameAttribute() + { + return __('CardDav Server') . ' ' . $this->id; + } + + public function getProvisioningSection($config, int $remoteContactDirectoryCounter) + { + $dom = $config->ownerDocument; + + $section = $dom->createElement('section'); + $section->setAttribute('name', 'remote_contact_directory_' . $remoteContactDirectoryCounter); + + $entry = $dom->createElement('entry', $this->enabled ? '1': '0'); + $entry->setAttribute('name', 'enabled'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', 'carddav'); + $entry->setAttribute('name', 'type'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->uri); + $entry->setAttribute('name', 'uri'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->min_characters); + $entry->setAttribute('name', 'min_characters'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->results_limit); + $entry->setAttribute('name', 'results_limit'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->timeout); + $entry->setAttribute('name', 'timeout'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->delay); + $entry->setAttribute('name', 'delay'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->fields_for_user_input); + $entry->setAttribute('name', 'carddav_fields_for_user_input'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->fields_for_domain); + $entry->setAttribute('name', 'carddav_fields_for_domain'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $this->use_exact_match_policy ? '1': '0'); + $entry->setAttribute('name', 'carddav_use_exact_match_policy'); + $section->appendChild($entry); + + $config->appendChild($section); + } +} diff --git a/flexiapi/config/session.php b/flexiapi/config/session.php index 290fa69..3e9082d 100644 --- a/flexiapi/config/session.php +++ b/flexiapi/config/session.php @@ -18,7 +18,7 @@ return [ | */ - 'driver' => env('SESSION_DRIVER', 'cookie'), + 'driver' => env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- diff --git a/flexiapi/database/migrations/2025_08_20_083715_create_space_carddav_servers.php b/flexiapi/database/migrations/2025_08_20_083715_create_space_carddav_servers.php new file mode 100644 index 0000000..f325f8f --- /dev/null +++ b/flexiapi/database/migrations/2025_08_20_083715_create_space_carddav_servers.php @@ -0,0 +1,62 @@ +boolean('carddav_user_credentials')->default(false); + }); + + Schema::create('space_carddav_servers', function (Blueprint $table) { + $table->id(); + + $table->bigInteger('space_id')->unsigned(); + $table->foreign('space_id')->references('id') + ->on('spaces')->onDelete('cascade'); + + $table->boolean('enabled')->default(true); + $table->string('uri'); + $table->integer('min_characters')->default(3); + $table->integer('results_limit')->default(0); + $table->integer('timeout')->default(5); + $table->integer('delay')->default(500); + $table->string('fields_for_user_input')->nullable(); + $table->string('fields_for_domain')->nullable(); + $table->boolean('use_exact_match_policy')->default(false); + $table->timestamps(); + }); + + Schema::create('account_carddav_credentials', function (Blueprint $table) { + $table->string('username', 64); + $table->string('password', 255); + $table->string('domain', 255); + $table->string('algorithm', 10)->default('MD5'); + + $table->bigInteger('space_carddav_server_id')->unsigned(); + $table->foreign('space_carddav_server_id')->references('id') + ->on('space_carddav_servers')->onDelete('cascade'); + + $table->integer('account_id')->unsigned(); + $table->foreign('account_id')->references('id') + ->on('accounts')->onDelete('cascade'); + $table->timestamps(); + + $table->unique(['space_carddav_server_id', 'account_id'], 'account_carddav_credentials_unique'); + }); + } + + public function down(): void + { + Schema::table('spaces', function (Blueprint $table) { + $table->dropColumn('carddav_user_credentials'); + }); + + Schema::dropIfExists('account_carddav_credentials'); + Schema::dropIfExists('space_carddav_servers'); + } +}; diff --git a/flexiapi/lang/fr.json b/flexiapi/lang/fr.json index a5ad652..47a70d5 100644 --- a/flexiapi/lang/fr.json +++ b/flexiapi/lang/fr.json @@ -41,6 +41,9 @@ "Calls logs": "Journaux d'appel", "Cancel": "Annuler", "Cannot be changed once created.": "Ne peut être changé par la suite.", + "CardDav credentials": "Identification CardDav", + "CardDav Server": "Serveur CardDav", + "CardDav Servers": "Serveurs CardDav", "Change your email": "Changer votre email", "Change your phone number": "Changer votre numéro de téléphone", "Check the README.md documentation": "Voir la documentation dans README.md", @@ -71,6 +74,7 @@ "Day": "Jour", "Deactivate All": "Tout désactiver", "Deactivated": "Désactivé", + "Delay in milliseconds before submiting the request": "Temps en millisecondes avant d'envoyer la requête", "Delete": "Supprimer", "Description": "Description", "Devices": "Appareils", @@ -84,6 +88,7 @@ "Email": "Email", "Empty": "Vide", "Enable the web interface": "Activer l'interface web", + "Enable user credentials for CardDav servers": "Activer les identifiants utilisateur pour les serveurs CardDav", "Enabled": "Activé", "Encrypted": "Chiffré", "Enter the code you received below": "Saisissez le code reçu ci-dessous", @@ -118,6 +123,9 @@ "Key": "Clef", "Last used": "Dernière utilisation", "Leave empty to create a root Space.": "Laisser vide si vous souhaitez créer un Espace à la racine", + "Limit the number of results": "Limiter le nomber de résultats", + "List of vcard fields to match for SIP domain": "Liste des champs vcard à matcher pour le domaine SIP", + "List of vcard fields to match with user input": "Liste des champs vcard à matcher avec les entrées utilisateur", "Login to my account":"Connexion à mon compte", "Login using a QRCode": "S'authentifier avec un QRCode", "Login": "Authentification", @@ -125,6 +133,7 @@ "Markdown text": "Texte Markdown", "Max accounts": "Maximum de comptes", "Meeting": "Réunions", + "Min characters to search": "Nombre minimum de caractères pour chercher", "Month": "Mois", "My Account": "Mon Compte", "My Space": "Mon Espace", @@ -168,6 +177,7 @@ "Remote provisioning": "Configuration distante", "Remove": "Remove", "Renew": "Renouveller", + "Request timeout in seconds": "Expiration de la requête en secondes", "Requests": "Requêtes", "Resend": "Renvoyer", "Reset my password":"Réinitialiser mon mot de passe", @@ -185,6 +195,7 @@ "Send an email to the user to reset the password": "Envoyer un email à l'utilisateur pour réinitialiser son mot de passe", "Send an email to the user with provisioning information": "Envoyer un email à l'utilisateur avec les informations de déploiement", "Send": "Envoyer", + "Separated by commas": "Séparé par des virgules", "Settings": "Paramètres", "Show usernames only": "Afficher uniquement les noms d'utilisateur", "SIP address":"Adresse SIP", @@ -224,6 +235,7 @@ "Updated on": "Mis à jour le", "Updated": "Mise à jour", "Use ; to comment, key=\"value\" to declare a complex string.": "Utilisez ; pour commenter, clef=\"valeur\" pour déclarer une chaine complexe.", + "Use exact match policy": "Utiliser la rêgle de recherche exacte", "Use the mobile app to recover your account using your phone number": "Utilisez l'application mobile pour récupérer votre compte avec votre numéro de téléphone", "Used on": "Utilisé le", "User": "Utilisateur", @@ -243,6 +255,7 @@ "Welcome on :app_name": "Bienvenue sur :app_name", "Welcome to :space: Start using your account today": "Bienvenue sur :space : commencez à utiliser votre compte dès maintenant", "Welcome to :space":"Bienvenue sur :space", + "Whether match must be exact or approximate (ignoring case, accents…)": "Est-ce que la recherche doit être exacte ou approximative (ignorer les majuscules, les accents…)", "Year": "Année", "You already have an account?": "Vous avez déjà un compte ?", "You are going to permanently delete the following element. Please confirm your action.": "Vous allez supprimer l'élément suivant. Veuillez confirmer votre action.", diff --git a/flexiapi/resources/views/admin/account/account_type/create.blade.php b/flexiapi/resources/views/admin/account/account_type/create.blade.php index 7e4a319..846f280 100644 --- a/flexiapi/resources/views/admin/account/account_type/create.blade.php +++ b/flexiapi/resources/views/admin/account/account_type/create.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) diff --git a/flexiapi/resources/views/admin/account/action/create_edit.blade.php b/flexiapi/resources/views/admin/account/action/create_edit.blade.php index 913636f..553beda 100644 --- a/flexiapi/resources/views/admin/account/action/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/action/create_edit.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) diff --git a/flexiapi/resources/views/admin/account/action/delete.blade.php b/flexiapi/resources/views/admin/account/action/delete.blade.php index 1fd781d..28fee76 100644 --- a/flexiapi/resources/views/admin/account/action/delete.blade.php +++ b/flexiapi/resources/views/admin/account/action/delete.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) diff --git a/flexiapi/resources/views/admin/account/activity/index.blade.php b/flexiapi/resources/views/admin/account/activity/index.blade.php index c8a4ab4..dcdedb6 100644 --- a/flexiapi/resources/views/admin/account/activity/index.blade.php +++ b/flexiapi/resources/views/admin/account/activity/index.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/carddav/create.blade.php b/flexiapi/resources/views/admin/account/carddav/create.blade.php new file mode 100644 index 0000000..a2625f1 --- /dev/null +++ b/flexiapi/resources/views/admin/account/carddav/create.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.main') + +@section('breadcrumb') + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) + +@endsection + +@section('content') +

{{ __('CardDav credentials') }}

+ +
+ @csrf +
+ + + @include('parts.errors', ['name' => 'carddav_id']) +
+ +
+ + + @include('parts.errors', ['name' => 'username']) +
+
+ + + @include('parts.errors', ['name' => 'domain']) +
+
+ + + @include('parts.errors', ['name' => 'password']) +
+ +
+ + +
+ +
+ +
+
+@endsection diff --git a/flexiapi/resources/views/admin/account/carddav/delete.blade.php b/flexiapi/resources/views/admin/account/carddav/delete.blade.php new file mode 100644 index 0000000..d01a5ad --- /dev/null +++ b/flexiapi/resources/views/admin/account/carddav/delete.blade.php @@ -0,0 +1,31 @@ +@extends('layouts.main') + +@section('breadcrumb') + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) + + +@endsection + +@section('content') +

{{ __('CardDav credentials') }}

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

{{ __('You are going to permanently delete the following element. Please confirm your action.') }}

+

{{ $carddavCredentials->identifier }}

+
+ + + + +
+ +
+
+@endsection diff --git a/flexiapi/resources/views/admin/account/contact/create.blade.php b/flexiapi/resources/views/admin/account/contact/create.blade.php index 3c97f37..ce1fb0e 100644 --- a/flexiapi/resources/views/admin/account/contact/create.blade.php +++ b/flexiapi/resources/views/admin/account/contact/create.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/contact/delete.blade.php b/flexiapi/resources/views/admin/account/contact/delete.blade.php index 4dadb0b..e047aa6 100644 --- a/flexiapi/resources/views/admin/account/contact/delete.blade.php +++ b/flexiapi/resources/views/admin/account/contact/delete.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) diff --git a/flexiapi/resources/views/admin/account/contact/index.blade.php b/flexiapi/resources/views/admin/account/contact/index.blade.php index 2cd5503..afad8f2 100644 --- a/flexiapi/resources/views/admin/account/contact/index.blade.php +++ b/flexiapi/resources/views/admin/account/contact/index.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php index 6d63095..46acd49 100644 --- a/flexiapi/resources/views/admin/account/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/create_edit.blade.php @@ -1,9 +1,11 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') @if ($account->id) - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @else diff --git a/flexiapi/resources/views/admin/account/delete.blade.php b/flexiapi/resources/views/admin/account/delete.blade.php index 7e7e01f..7b6988d 100644 --- a/flexiapi/resources/views/admin/account/delete.blade.php +++ b/flexiapi/resources/views/admin/account/delete.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/device/delete.blade.php b/flexiapi/resources/views/admin/account/device/delete.blade.php index 2dce50b..af50d51 100644 --- a/flexiapi/resources/views/admin/account/device/delete.blade.php +++ b/flexiapi/resources/views/admin/account/device/delete.blade.php @@ -1,13 +1,12 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection @section('content') -

{{ __('Delete') }}

+

{{ __('Delete') }}

{{ __('You are going to permanently delete the following element. Please confirm your action.') }}

diff --git a/flexiapi/resources/views/admin/account/dictionary/create_edit.blade.php b/flexiapi/resources/views/admin/account/dictionary/create_edit.blade.php index 992e907..c490462 100644 --- a/flexiapi/resources/views/admin/account/dictionary/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/dictionary/create_edit.blade.php @@ -1,17 +1,12 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection @section('content') - @if ($entry->id) -

{{ __('Edit') }}

- @else -

{{ __('Create') }}

- @endif +

{{ __('Dictionary') }}

$account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection @section('content') -

{{ __('Delete') }}

+

{{ __('Delete') }}

{{ __('You are going to permanently delete the following element. Please confirm your action.') }}

diff --git a/flexiapi/resources/views/admin/account/external/delete.blade.php b/flexiapi/resources/views/admin/account/external/delete.blade.php index 279cff7..01c1826 100644 --- a/flexiapi/resources/views/admin/account/external/delete.blade.php +++ b/flexiapi/resources/views/admin/account/external/delete.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/external/show.blade.php b/flexiapi/resources/views/admin/account/external/show.blade.php index abf5662..403f2c9 100644 --- a/flexiapi/resources/views/admin/account/external/show.blade.php +++ b/flexiapi/resources/views/admin/account/external/show.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/import/check.blade.php b/flexiapi/resources/views/admin/account/import/check.blade.php index ba17ca2..577b730 100644 --- a/flexiapi/resources/views/admin/account/import/check.blade.php +++ b/flexiapi/resources/views/admin/account/import/check.blade.php @@ -1,7 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') @endsection diff --git a/flexiapi/resources/views/admin/account/import/create.blade.php b/flexiapi/resources/views/admin/account/import/create.blade.php index e16fe98..9091af6 100644 --- a/flexiapi/resources/views/admin/account/import/create.blade.php +++ b/flexiapi/resources/views/admin/account/import/create.blade.php @@ -1,7 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') @endsection diff --git a/flexiapi/resources/views/admin/account/provisioning_email/create.blade.php b/flexiapi/resources/views/admin/account/provisioning_email/create.blade.php index de9423d..3367c90 100644 --- a/flexiapi/resources/views/admin/account/provisioning_email/create.blade.php +++ b/flexiapi/resources/views/admin/account/provisioning_email/create.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php b/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php index e421d35..d6e893f 100644 --- a/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php +++ b/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/show.blade.php b/flexiapi/resources/views/admin/account/show.blade.php index 85999e1..45e8cc0 100644 --- a/flexiapi/resources/views/admin/account/show.blade.php +++ b/flexiapi/resources/views/admin/account/show.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection @section('content') @@ -81,7 +80,7 @@ + @if ($account->space->carddav_user_credentials) +
+ @if ($account->remainingCardDavCredentialsCreatable->count() > 0) + + + {{ __('Add') }} + + @endif +

+ {{ __('CardDav credentials') }} +

+ + + + + + + + + + + + @if ($account->carddavServers->isEmpty()) + + + + @else + @foreach ($account->carddavServers as $carddavServer) + + + + + + + + @endforeach + @endif + +
{{ __('CardDav Server') }}{{ __('Username') }}{{ __('Domain') }}{{ __('Algorithm') }}
{{ __('Empty') }}
{{ $carddavServer->name }}{{ $carddavServer->pivot->username }}{{ $carddavServer->pivot->domain }}{{ $carddavServer->pivot->algorithm }} + + + +
+
+ + @endif +

{{ __('Devices') }} @@ -144,7 +190,7 @@ @if ($devices->isEmpty()) - {{ __('Empty') }} + {{ __('Empty') }} @else @foreach ($devices as $device) diff --git a/flexiapi/resources/views/admin/account/statistics/show.blade.php b/flexiapi/resources/views/admin/account/statistics/show.blade.php index 5b19f70..cc0c9fb 100644 --- a/flexiapi/resources/views/admin/account/statistics/show.blade.php +++ b/flexiapi/resources/views/admin/account/statistics/show.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/statistics/show_call_logs.blade.php b/flexiapi/resources/views/admin/account/statistics/show_call_logs.blade.php index 766866e..ca4acfe 100644 --- a/flexiapi/resources/views/admin/account/statistics/show_call_logs.blade.php +++ b/flexiapi/resources/views/admin/account/statistics/show_call_logs.blade.php @@ -1,8 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') - @include('admin.account.parts.breadcrumb_accounts_show', ['account' => $account]) + @include('admin.parts.breadcrumb.accounts.show', ['account' => $account]) @endsection diff --git a/flexiapi/resources/views/admin/account/type/create_edit.blade.php b/flexiapi/resources/views/admin/account/type/create_edit.blade.php index 0ec82fb..781b7db 100644 --- a/flexiapi/resources/views/admin/account/type/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/type/create_edit.blade.php @@ -1,7 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') diff --git a/flexiapi/resources/views/admin/account/type/delete.blade.php b/flexiapi/resources/views/admin/account/type/delete.blade.php index 9ed55c7..1b1822b 100644 --- a/flexiapi/resources/views/admin/account/type/delete.blade.php +++ b/flexiapi/resources/views/admin/account/type/delete.blade.php @@ -1,7 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') diff --git a/flexiapi/resources/views/admin/account/type/index.blade.php b/flexiapi/resources/views/admin/account/type/index.blade.php index 93df008..9b8f369 100644 --- a/flexiapi/resources/views/admin/account/type/index.blade.php +++ b/flexiapi/resources/views/admin/account/type/index.blade.php @@ -1,7 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.parts.breadcrumb.accounts.index') diff --git a/flexiapi/resources/views/admin/account/parts/breadcrumb_accounts_index.blade.php b/flexiapi/resources/views/admin/parts/breadcrumb/accounts/index.blade.php similarity index 100% rename from flexiapi/resources/views/admin/account/parts/breadcrumb_accounts_index.blade.php rename to flexiapi/resources/views/admin/parts/breadcrumb/accounts/index.blade.php diff --git a/flexiapi/resources/views/admin/account/parts/breadcrumb_accounts_show.blade.php b/flexiapi/resources/views/admin/parts/breadcrumb/accounts/show.blade.php similarity index 68% rename from flexiapi/resources/views/admin/account/parts/breadcrumb_accounts_show.blade.php rename to flexiapi/resources/views/admin/parts/breadcrumb/accounts/show.blade.php index 2c73d60..4a4a228 100644 --- a/flexiapi/resources/views/admin/account/parts/breadcrumb_accounts_show.blade.php +++ b/flexiapi/resources/views/admin/parts/breadcrumb/accounts/show.blade.php @@ -1,3 +1,4 @@ +@include('admin.parts.breadcrumb.accounts.index') \ No newline at end of file diff --git a/flexiapi/resources/views/admin/parts/breadcrumb/spaces/index.blade.php b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/index.blade.php new file mode 100644 index 0000000..e10b44d --- /dev/null +++ b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/index.blade.php @@ -0,0 +1,5 @@ +@if (auth()->user()->superAdmin) + +@endif \ No newline at end of file diff --git a/flexiapi/resources/views/admin/parts/breadcrumb/spaces/integration.blade.php b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/integration.blade.php new file mode 100644 index 0000000..77face3 --- /dev/null +++ b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/integration.blade.php @@ -0,0 +1,4 @@ +@include('admin.parts.breadcrumb.spaces.show') + \ No newline at end of file diff --git a/flexiapi/resources/views/admin/parts/breadcrumb/spaces/show.blade.php b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/show.blade.php new file mode 100644 index 0000000..f055b02 --- /dev/null +++ b/flexiapi/resources/views/admin/parts/breadcrumb/spaces/show.blade.php @@ -0,0 +1,8 @@ +@include('admin.parts.breadcrumb.spaces.index') + \ No newline at end of file diff --git a/flexiapi/resources/views/admin/space/administration.blade.php b/flexiapi/resources/views/admin/space/administration.blade.php index c2010b2..40f131f 100644 --- a/flexiapi/resources/views/admin/space/administration.blade.php +++ b/flexiapi/resources/views/admin/space/administration.blade.php @@ -1,14 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - - + @include('admin.parts.breadcrumb.spaces.show') @endsection @@ -48,6 +41,12 @@ @include('parts.form.toggle', ['object' => $space, 'key' => 'super', 'label' => __('Super Space'), 'supporting' => __('All the admins will be super admins')])

+

{{ __('Integration') }}

+ +
+ @include('parts.form.toggle', ['object' => $space, 'key' => 'carddav_user_credentials', 'label' => __('Enable user credentials for CardDav servers')]) +
+

Interface

@include('parts.form.toggle', ['object' => $space, 'key' => 'custom_theme', 'label' => __('Allow a custom CSS theme'), 'supporting' => __('Check the README.md documentation')]) diff --git a/flexiapi/resources/views/admin/space/carddav_server/create_edit.blade.php b/flexiapi/resources/views/admin/space/carddav_server/create_edit.blade.php new file mode 100644 index 0000000..8555c54 --- /dev/null +++ b/flexiapi/resources/views/admin/space/carddav_server/create_edit.blade.php @@ -0,0 +1,98 @@ +@extends('layouts.main') + +@section('breadcrumb') + @include('admin.parts.breadcrumb.spaces.integration') + +@endsection + +@section('content') +
+

+ {{ __('CardDav Server') }} - + @if ($carddavServer->id) + {{ __('Edit') }} + @else + {{ __('Create') }} + @endif +

+
+ + + @csrf + @method($carddavServer->id ? 'put' : 'post') + +
+ + + @include('parts.errors', ['name' => 'uri']) +
+ + @include('parts.form.toggle', ['object' => $carddavServer, 'key' => 'enabled', 'label' => __('Enabled')]) + +
+ +
+ + + @include('parts.errors', ['name' => 'min_characters']) +
+ +
+ + + {{ __('Unlimited if set to 0') }} + @include('parts.errors', ['name' => 'results_limit']) +
+ + @include('parts.form.toggle', ['object' => $carddavServer, 'key' => 'use_exact_match_policy', 'label' => __('Use exact match policy'), 'supporting' => __('Whether match must be exact or approximate (ignoring case, accents…)')]) + +
isNotEmpty())open @endif> + +

+ {{ __('Other information') }} +

+
+
+
+ + + @include('parts.errors', ['name' => 'timeout']) +
+ +
+ + + @include('parts.errors', ['name' => 'delay']) +
+ +
+ + + {{ __('Separated by commas') }} + @include('parts.errors', ['name' => 'fields_for_user_input']) +
+ +
+ + + {{ __('Separated by commas') }} + @include('parts.errors', ['name' => 'fields_for_domain']) +
+
+
+ +
+ @if ($carddavServer->id) + + @else + + @endif +
+ + +@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/space/carddav_server/delete.blade.php b/flexiapi/resources/views/admin/space/carddav_server/delete.blade.php new file mode 100644 index 0000000..54647b8 --- /dev/null +++ b/flexiapi/resources/views/admin/space/carddav_server/delete.blade.php @@ -0,0 +1,28 @@ +@extends('layouts.main') + +@section('breadcrumb') + @include('admin.parts.breadcrumb.spaces.integration') + +@endsection + +@section('content') +
+

{{ __('CardDav Server') }} - {{ __('Delete') }}

+ + {{ __('Cancel') }} + +
+
+ @csrf + @method('delete') + +
+

{{ __('You are going to permanently delete the following element. Please confirm your action.') }}
+ {{ $carddavServer->uri }} +

+ +
+
+
+
+@endsection diff --git a/flexiapi/resources/views/admin/space/configuration.blade.php b/flexiapi/resources/views/admin/space/configuration.blade.php index 570fb9e..3c0adee 100644 --- a/flexiapi/resources/views/admin/space/configuration.blade.php +++ b/flexiapi/resources/views/admin/space/configuration.blade.php @@ -2,14 +2,7 @@ @section('breadcrumb') @if (auth()->user()->superAdmin) - - + @include('admin.parts.breadcrumb.spaces.show') @else + @include('admin.parts.breadcrumb.spaces.index') @endsection diff --git a/flexiapi/resources/views/admin/space/delete.blade.php b/flexiapi/resources/views/admin/space/delete.blade.php index 4677222..d13fa41 100644 --- a/flexiapi/resources/views/admin/space/delete.blade.php +++ b/flexiapi/resources/views/admin/space/delete.blade.php @@ -1,9 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - + @include('admin.parts.breadcrumb.spaces.show') @endsection diff --git a/flexiapi/resources/views/admin/space/edit.blade.php b/flexiapi/resources/views/admin/space/edit.blade.php index 56dcc7d..fb0714a 100644 --- a/flexiapi/resources/views/admin/space/edit.blade.php +++ b/flexiapi/resources/views/admin/space/edit.blade.php @@ -1,14 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - - + @include('admin.parts.breadcrumb.spaces.show') @endsection diff --git a/flexiapi/resources/views/admin/space/email_server/delete.blade.php b/flexiapi/resources/views/admin/space/email_server/delete.blade.php index 2b514fb..961fbe2 100644 --- a/flexiapi/resources/views/admin/space/email_server/delete.blade.php +++ b/flexiapi/resources/views/admin/space/email_server/delete.blade.php @@ -1,23 +1,13 @@ @extends('layouts.main') @section('breadcrumb') - - - + @include('admin.parts.breadcrumb.spaces.integration') @endsection @section('content')
-

{{ __('Delete') }}

+

{{ __('Email Server') }} - {{ __('Delete') }}

{{ __('Cancel') }} diff --git a/flexiapi/resources/views/admin/space/email_server/show.blade.php b/flexiapi/resources/views/admin/space/email_server/show.blade.php index 15185a9..46d1db0 100644 --- a/flexiapi/resources/views/admin/space/email_server/show.blade.php +++ b/flexiapi/resources/views/admin/space/email_server/show.blade.php @@ -1,17 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - - - + @include('admin.parts.breadcrumb.spaces.integration') @endsection @@ -20,7 +10,6 @@

{{ $space->name }}

-
diff --git a/flexiapi/resources/views/admin/space/integration.blade.php b/flexiapi/resources/views/admin/space/integration.blade.php index ffe97e4..441a4a3 100644 --- a/flexiapi/resources/views/admin/space/integration.blade.php +++ b/flexiapi/resources/views/admin/space/integration.blade.php @@ -1,15 +1,7 @@ @extends('layouts.main') @section('breadcrumb') - - - + @include('admin.parts.breadcrumb.spaces.integration') @endsection @section('content') @@ -36,4 +28,29 @@

+ +
+ + + + {{ __('Create') }} + + +

{{ __('CardDav Servers') }}

+ +
+ @foreach ($space->carddavServers as $carddavServer) +
+ {{ $carddavServer->accounts()->count() }} + +

{{ $carddavServer->name }}

+

+ {{ $carddavServer->uri}}
+
+ {{ __('Edit') }} + {{ __('Delete') }} +

+
+ @endforeach +
@endsection diff --git a/flexiapi/resources/views/admin/space/show.blade.php b/flexiapi/resources/views/admin/space/show.blade.php index 5acf749..46e260b 100644 --- a/flexiapi/resources/views/admin/space/show.blade.php +++ b/flexiapi/resources/views/admin/space/show.blade.php @@ -2,9 +2,7 @@ @section('breadcrumb') @if (auth()->user()->superAdmin) - + @include('admin.parts.breadcrumb.spaces.index') @endif diff --git a/flexiapi/resources/views/api/documentation/about_auth.blade.php b/flexiapi/resources/views/api/documentation/about_auth.blade.php new file mode 100644 index 0000000..1475814 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/about_auth.blade.php @@ -0,0 +1,115 @@ +# About & Auth. + +An API to deal with the Flexisip server. + +The API is available under `/api` + +A `content-type` and `accept` HTTP headers are REQUIRED to use the API properly + +``` +> GET /api/{endpoint} +> content-type: application/json +> accept: application/json +``` + +
+ +Restricted endpoints are protected using a DIGEST authentication or an API Key mechanisms. + +### Access model + +The endpoints are accessible using three different models: + +- Public publicly accessible +- User the endpoint can only be accessed by an authenticated user +- Admin the endpoint can be only be accessed by an authenticated admin user +- Super Admin the endpoint can be only be accessed by an authenticated super admin user + +### Space expiration + +Super Admin 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. + +Currently supported languages: @php + echo implode(', ', config('app.authorized_locales')) +@endphp + +``` +> GET /api/{endpoint} +> Accept-Language: fr +> … + +< HTTP 422 +< { +< "message": "Le champ pseudo est obligatoire.", +< "errors": { +< "username": [ +< 0 => "Le champ pseudo est obligatoire." +< ] +< } +< } + +``` + +### Using the API Key + +You can retrieve an API Key from your account panel or using the dedicated API endpoint. + +**When generated by a User account the generated API Key will be restricted to the IP that generates it and will be destroyed if not used after some times.** + +**If you want a stable API Key, to integrate with another server component for example, you can generate one in the Administration Panel.** + +You can then use your freshly generated key by adding a new `x-api-key` header to your API requests: + +``` +> GET /api/{endpoint} +> x-api-key: {your-api-key} +> … +``` + +Or using a cookie: + +``` +> GET /api/{endpoint} +> Cookie: x-api-key={your-api-key} +> … +``` + +### Using a JWT token + +You can use a JWT token to authenticate on the API. + +To do so you MUST inject it as an `Authorization: Bearer` header and configure the API with the public key of the token emitter. + +``` +> GET /api/{endpoint} +> Authorization: Bearer {your-jwt-token} +> … +``` + +The API will then check if the token was signed properly, is still valid and authenticate a user that is actually available in the system. + +### Using DIGEST + +To discover the available hashing algorythm you MUST send an unauthenticated request to one of the restricted endpoints.
+Only DIGEST-MD5 and DIGEST-SHA-256 are supported through the authentication layer. + +A `from` (consisting of the user SIP address, prefixed with `sip:`) header is required to initiate the DIGEST flow. + +``` +> GET /api/{restricted-endpoint} +> from: sip:foobar@sip.example.org +> … + +< HTTP 401 +< content-type: application/json +< www-authenticate: Digest realm=test,qop=auth,algorithm=MD5,nonce="{nonce}",opaque="{opaque}" +< www-authenticate: Digest realm=test,qop=auth,algorithm=SHA-256,nonce="{nonce}",opaque="{opaque}" +``` + +You can find more documentation on the related [IETF RFC-7616](https://tools.ietf.org/html/rfc7616). + +
diff --git a/flexiapi/resources/views/api/documentation/account_tokens.blade.php b/flexiapi/resources/views/api/documentation/account_tokens.blade.php new file mode 100644 index 0000000..836a565 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/account_tokens.blade.php @@ -0,0 +1,91 @@ +## Account Creation Request Tokens + +An `account_creation_request_token` is a unique token that can be validated and then used to generate a valid `account_creation_token`. + +### `POST /account_creation_request_tokens` +Public + +Create and return an `account_creation_request_token` that should then be validated to be used. + +## Account Creation Tokens + +An `account_creation_token` is a unique token that allow the creation or the validation of a unique account. + +### `POST /account_creation_tokens/send-by-push` +Public + +Create and send an `account_creation_token` using a push notification to the device. +Return `403` if a token was already sent, or if the tokens limit is reached for this device. +Return `503` if the token was not successfully sent. + +JSON parameters: + +* `pn_provider` **required**, the push notification provider, must be in apns.dev, apns or fcm +* `pn_param` the push notification parameter, can be null or contain only alphanumeric and underscore characters +* `pn_prid` the push notification unique id, can be null or contain only alphanumeric, dashes, underscore and colon characters + +### `POST /account_creation_tokens/using-account-creation-request-token` +Public + +Create an `account_creation_token` using an `account_creation_request_token`. +Return an `account_creation_token`. +Return `404` if the `account_creation_request_token` provided is not valid or expired otherwise. + +JSON parameters: + +* `account_creation_request_token` **required** + +### `POST /account_creation_tokens/consume` +User + +Consume an `account_creation_token` and link it to the authenticated account. +Return an `account_creation_token`. + +Return `404` if the `account_creation_token` provided is not valid. + +JSON parameters: + +* `account_creation_token` **required** + +### `POST /account_creation_tokens` +Admin + +Create and return an `account_creation_token`. + +## Account Recovery Tokens + +An `account_recovery_token` is a unique token that allow the recovery of an account. + +It can be used on the following page that also accepts a `phone` optional parameter to prefil the recovery form: + + {{ route('account.recovery.show.phone', ['account_recovery_token' => '_the_token_']) }} + {{ route('account.recovery.show.phone', ['account_recovery_token' => '_the_token_', 'phone' => '+3312341234']) }} + +### `POST /account_recovery_tokens/send-by-push` +Public + +Create and send an `account_recovery_token` using a push notification to the device. +Return `403` if a token was already sent, or if the tokens limit is reached for this device. +Return `503` if the token was not successfully sent. + +JSON parameters: + +* `pn_provider` **required**, the push notification provider, must be in apns.dev, apns or fcm +* `pn_param` the push notification parameter, can be null or contain only alphanumeric and underscore characters +* `pn_prid` the push notification unique id, can be null or contain only alphanumeric, dashes, underscore and colon characters + +## Auth Tokens + +### `POST /accounts/auth_token` +Public + +Generate an `auth_token`. To attach the generated token to an account see [`auth_token` attachement endpoint](#get-accountsauthtokenauthtokenattach). + +Return the `auth_token` object. + +### `GET /accounts/auth_token/{auth_token}/attach` +User + +Attach a publicly generated authentication token to the currently authenticated account. + +Return `404` if the token is non existing or invalid. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts.blade.php b/flexiapi/resources/views/api/documentation/accounts.blade.php new file mode 100644 index 0000000..11056ad --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts.blade.php @@ -0,0 +1,176 @@ +## Accounts + +### `POST /accounts/with-account-creation-token` +Public + +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 +* `password` **required** minimum 6 characters +* `algorithm` **required**, values can be `SHA-256` or `MD5` +* `account_creation_token` the unique `account_creation_token` +* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` + +### `GET /accounts/{sip}/info` +Public + +Retrieve public information about the account. +Return `404` if the account doesn't exists. + +### `GET /accounts/me/api_key/{auth_token}` +Public + +Generate and retrieve a fresh API Key from an `auth_token`. The `auth_token` must be attached to an existing account, see [`auth_token` attachement endpoint](#get-accountsauthtokenauthtokenattach) to do so. + +Return `404` if the token is invalid or not attached. + +This endpoint is also setting the API Key as a Cookie. + +### `GET /accounts/me/api_key` +User + +Generate and retrieve a fresh API Key. +This endpoint is also setting the API Key as a Cookie. + +### `GET /accounts/me` +User + +Retrieve the account information. + +### `GET /accounts/me/services/turn` +User + +If configured, returns valid TURN credentials following the [draft-uberti-behave-turn-rest-00 IEFT Draft](https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00). + +### `GET /accounts/me/provision` +User + +Provision the account by generating a fresh `provisioning_token`. + +Return the account object. + +### `DELETE /accounts/me` +User + +Delete the account. + +### `POST /accounts/me/password` +User + +Change the account password. + +JSON parameters: + +* `algorithm` **required**, values can be `SHA-256` or `MD5` +* `old_password` **required** if the password is already set, the old password +* `password` **required**, the new password + +### `POST /accounts` +Admin + +To create an account directly from the API. + +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**, 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` +* `admin` optional, a boolean, set to `false` by default, create an admin account +* `phone` optional, a valid phone number, set a phone number to the account +* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` +* `dictionary` optional, an associative array attached to the account, see also the related endpoints. + +### `PUT /accounts/{id}` +Admin + +Update an existing account. Ensure to resend all the parameters to not reset them. + +JSON parameters: + +* `username` unique username, minimum 6 characters +* `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 +* `email` optional, must be an email, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` +* `admin` optional, a boolean, set to `false` by default +* `phone` optional, a valid phone number, set a phone number to the account +* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` + +Using this endpoint you can also set a fresh dictionnary if the parameter is set. The existing dictionary entries will be destroyed. + +* `dictionary` optional, an associative array attached to the account, see also the related endpoints. + +This endpoint also return the current `phone_change_code` and `email_change_code` if they are available. + +### `GET /accounts` +Admin + +Retrieve all the accounts, paginated. + +### `GET /accounts/{id}` +Admin + +Retrieve a specific account. + +### `GET /accounts/{sip}/search` +Admin + +Search for a specific account by sip address. + +### `GET /accounts/{email}/search-by-email` +Admin + +Search for a specific account by email. + +### `DELETE /accounts/{id}` +Admin + +Delete a specific account and its related information. + +### `POST /accounts/{id}/activate` +Admin + +Activate an account. + +### `POST /accounts/{id}/deactivate` +Admin + +Deactivate an account. + +### `POST /accounts/{id}/block` +Admin + +Block an account. + +### `POST /accounts/{id}/unblock` +Admin + +Unblock an account. + +### `GET /accounts/{id}/provision` +Admin + +Provision an account by generating a fresh `provisioning_token`. + +### `POST /accounts/{id}/send_provisioning_email` +Admin + +Send a provisioning email to the account. + +### `POST /accounts/{id}/send_reset_password_email` +Admin + +Send a password reset email to the account. diff --git a/flexiapi/resources/views/api/documentation/accounts/actions.blade.php b/flexiapi/resources/views/api/documentation/accounts/actions.blade.php new file mode 100644 index 0000000..df6b9a5 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/actions.blade.php @@ -0,0 +1,38 @@ +## Account actions + +The following endpoints will return `403 Forbidden` if the requested account doesn't have a DTMF protocol configured. + +### `GET /accounts/{id}/actions` +Admin + +Show an account related actions. + +### `GET /accounts/{id}/actions/{action_id}` +Admin + +Show an account related action. + +### `POST /accounts/{id}/actions/` +Admin + +Create an account action. + +JSON parameters: + +* `key` **required**, alpha numeric with dashes, lowercase +* `code` **required**, alpha numeric, lowercase + +### `PUT /accounts/{id}/actions/{action_id}` +Admin + +Create an account action. + +JSON parameters: + +* `key` **required**, alpha numeric with dashes, lowercase +* `code` **required**, alpha numeric, lowercase + +### `DELETE /accounts/{id}/actions/{action_id}` +Admin + +Delete an account related action. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/carddav_credentials.blade.php b/flexiapi/resources/views/api/documentation/accounts/carddav_credentials.blade.php new file mode 100644 index 0000000..baed75c --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/carddav_credentials.blade.php @@ -0,0 +1,30 @@ +## Account CardDav credentials + +The following endpoints will return `403 Forbidden` if the requested account doesn't have a DTMF protocol configured. + +### `GET /accounts/{id}/carddavs` +Admin + +Show an account CardDav servers credentials. + +### `GET /accounts/{id}/carddavs/{carddav_id}` +Admin + +Show an account CardDav server credentials. + +### `PUT /accounts/{id}/carddavs/{carddav_id}` +Admin + +Create an account CardDav server credentials. + +JSON parameters: + +* `username` **required** the username +* `password` **required** the password in plain text +* `algorithm` **required**, values can be `SHA-256` or `MD5` +* `domain` **required** the domain + +### `DELETE /accounts/{id}/carddavs/{carddav_id}` +Admin + +Delete an account related action. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/contacts.blade.php b/flexiapi/resources/views/api/documentation/accounts/contacts.blade.php new file mode 100644 index 0000000..30417a9 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/contacts.blade.php @@ -0,0 +1,22 @@ +## Account contacts + +### `GET /accounts/{id/me}/contacts` +User +Admin + +Return the user contacts. + +### `GET /accounts/me/contacts/{sip}` +User + +Return a user contact. + +### `POST /accounts/{id}/contacts/{contact_id}` +Admin + +Add a contact to the list. + +### `DELETE /accounts/{id}/contacts/{contact_id}` +Admin + +Remove a contact from the list. diff --git a/flexiapi/resources/views/api/documentation/accounts/contacts_lists.blade.php b/flexiapi/resources/views/api/documentation/accounts/contacts_lists.blade.php new file mode 100644 index 0000000..7adfff8 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/contacts_lists.blade.php @@ -0,0 +1,56 @@ +## Contacts Lists + +### `GET /contacts_lists` +Admin + +Show all the contacts lists. + +### `GET /contacts_lists/{id}` +Admin + +Show a contacts list. + +### `POST /contacts_lists` +Admin + +Create a contacts list. + +JSON parameters: + +* `title` **required** +* `description` **required** + +### `PUT /contacts_lists/{id}` +Admin + +Update a contacts list. + +JSON parameters: + +* `title` **required** +* `description` **required** + +### `DELETE /contacts_lists/{id}` +Admin + +Delete a contacts list. + +### `POST /contacts_lists/{contacts_list_id}/contacts/{contact_id}` +Admin + +Add a contact to the contacts list. + +### `DELETE /contacts_lists/{contacts_list_id}/contacts/{contact_id}` +Admin + +Remove a contact from the contacts list. + +### `POST /accounts/{id}/contacts_lists/{contacts_list_id}` +Admin + +Add a contacts list to the account. + +### `DELETE /accounts/{id}/contacts_lists/{contacts_list_id}` +Admin + +Remove a contacts list from the account. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/dictionary.blade.php b/flexiapi/resources/views/api/documentation/accounts/dictionary.blade.php new file mode 100644 index 0000000..ca3fb0e --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/dictionary.blade.php @@ -0,0 +1,15 @@ +## Account dictionary + +### `GET /accounts/{id}/dictionary` +Admin + +Get all the account dictionary entries. + +### `POST /accounts/{id}/dictionary/{key}` +Admin + +Add or update a new entry to the dictionary + +JSON parameters: + +* `value` **required**, the entry value \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/email.blade.php b/flexiapi/resources/views/api/documentation/accounts/email.blade.php new file mode 100644 index 0000000..ce6d67e --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/email.blade.php @@ -0,0 +1,24 @@ +## Accounts email + +### `POST /accounts/me/email/request` +User + +Request to change the account email. An email will be sent to the new email address to confirm the operation. + +Will return `403` if the account doesn't have a validated Account Creation Token attached to it. + +JSON parameters: + +* `email` the new email address, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` + +### `POST /accounts/me/email` +User + +Confirm the code received and change the email. +Activate the account. + +JSON parameters: + +* `code` the code received by email + +Return the updated account. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/external_account.blade.php b/flexiapi/resources/views/api/documentation/accounts/external_account.blade.php new file mode 100644 index 0000000..19928a2 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/external_account.blade.php @@ -0,0 +1,26 @@ +## Account external Account + +### `GET /accounts/{id}/external` +Admin + +Get the external account. + +### `POST /accounts/{id}/external` +Admin + +Create or update the external account. + +JSON parameters: + +* `username` **required** +* `domain` **required** +* `password` **required** +* `realm` must be different than `domain` +* `registrar` must be different than `domain` +* `outbound_proxy` must be different than `domain` +* `protocol` **required**, must be `UDP`, `TCP` or `TLS` + +### `DELETE /accounts/{id}/external` +Admin + +Delete the external account. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/phone.blade.php b/flexiapi/resources/views/api/documentation/accounts/phone.blade.php new file mode 100644 index 0000000..999a882 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/phone.blade.php @@ -0,0 +1,38 @@ +## Account phone number + +### `POST /accounts/me/phone/request` +User + +Request a specific code by SMS to change the phone number. + +Will return `403` if the account doesn't have a validated Account Creation Token attached to it. + +JSON parameters: + +* `phone` the phone number to send the SMS + +### `POST /accounts/me/phone` +User + +Confirm the code received and change the phone number. +Activate the account. + +JSON parameters: + +* `code` the received SMS code + +Return the updated account. + +## Accounts devices + +### `GET /accounts/{id/me}/devices` +Admin +User + +Return the user registered devices. + +### `DELETE /accounts/{id/me}/devices/{uuid}` +Admin +User + +Remove one of the user registered devices. diff --git a/flexiapi/resources/views/api/documentation/accounts/types.blade.php b/flexiapi/resources/views/api/documentation/accounts/types.blade.php new file mode 100644 index 0000000..c20b8c3 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/types.blade.php @@ -0,0 +1,44 @@ +## Account Types + +### `GET /account_types` +Admin + +Show all the account types. + +### `GET /account_types/{id}` +Admin + +Show an account type. + +### `POST /account_types` +Admin + +Create an account type. + +JSON parameters: + +* `key` **required**, alpha numeric with dashes, lowercase + +### `PUT /account_types/{id}` +Admin + +Update an account type. + +JSON parameters: + +* `key` **required**, alpha numeric with dashes, lowercase + +### `DELETE /account_types/{id}` +Admin + +Delete an account type. + +### `POST /accounts/{id}/types/{type_id}` +Admin + +Add a type to the account. + +### `DELETE /accounts/{id}/contacts/{type_id}` +Admin + +Remove a type from the account. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/accounts/vcards_storage.blade.php b/flexiapi/resources/views/api/documentation/accounts/vcards_storage.blade.php new file mode 100644 index 0000000..646ee67 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/accounts/vcards_storage.blade.php @@ -0,0 +1,39 @@ +## Account vCards storage + +### `POST /accounts/{id/me}/vcards-storage` +Admin +User + +Store a vCard. + +JSON parameters: + +* `vcard`, mandatory, a valid vCard having a mandatory `UID` parameter that is uniquelly identifying it. This `UID` parameter will then be used to manipulate the vcard through the following endpoints as `uuid`. + +### `PUT /accounts/{id/me}/vcards-storage/{uuid}` +Admin +User + +Update a vCard. + +JSON parameters: + +* `vcard`, mandatory, a valid vCard having a mandatory `UID` parameter that is uniquelly identifying it and is the same as the `uuid` parameter. + +### `GET /accounts/{id/me}/vcards-storage` +Admin +User + +Return the list of stored vCards + +### `GET /accounts/{id/me}/vcards-storage/{uuid}` +Admin +User + +Return a stored vCard + +### `DELETE /accounts/{id/me}/vcards-storage/{uuid}` +Admin +User + +Delete a stored vCard \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/spaces.blade.php b/flexiapi/resources/views/api/documentation/spaces.blade.php new file mode 100644 index 0000000..270aae1 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/spaces.blade.php @@ -0,0 +1,95 @@ + +## Spaces + +Manage the list of allowed `spaces`. The admin accounts declared with a `domain` that is a `super` `sip_domain` will become Super Admin. + +### `GET /spaces` +Super Admin + +Get the list of declared Spaces. + +### `GET /spaces/{domain}` +Super Admin + +Get a Space. + +### `POST /spaces` +Super Admin + +Create a new `space`. + +JSON parameters: + +* `name` **required**, the space name +* `domain` **required**, the SIP domain to use, must be unique +* `host` **required**, the space host +* `account_proxy_registrar_address`, the account proxy registrar address +* `account_realm`, the default realm for the accounts, fallback to the domain if not set +* `assistant_disable_qr_code` boolean, disable the QR code feature in the assistant, default to `false` +* `assistant_hide_create_account` boolean, disable the account creation assistant, default to `false` +* `assistant_hide_third_party_account` boolean, disable the call recording feature, default to `false` +* `carddav_user_credentials` boolean, enable credentials for CardDav servers +* `copyright_text` text, the copyright text +* `custom_provisioning_entries` text, the custom configuration used for the provisioning +* `custom_provisioning_overwrite_all` boolean, allow the custom configuration to overwrite the default one +* `custom_theme` boolean, allow a custom CSS file to be loaded +* `disable_broadcast_feature` boolean, disable the broadcast feature, default to `true` +* `disable_call_recordings_feature` boolean, disable the call recording feature, default to `false` +* `disable_chat_feature` boolean, disable the chat feature, default to `false` +* `disable_meetings_feature` boolean, disable the meeting feature, default to `false` +* `expire_at` date, the moment the space is expiring, default to `null` (never expire) +* `hide_account_settings` boolean, disable the account settings, default to `false` +* `hide_settings` boolean, hide the app settings, default to `false` +* `intercom_features` boolean, the intercom features switch +* `intro_registration_text` Markdown text, the main registration page text +* `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 +* `newsletter_registration_address`, the newsletter registration email address +* `only_display_sip_uri_username` boolean, hide the SIP uris in the app, default to `false` +* `phone_registration` boolean, the phone registration switch +* `provisioning_use_linphone_provisioning_header` boolean +* `public_registration` boolean, the public registration switch +* `super` boolean, set the domain as a Super Domain +* `web_panel` boolean, the web panel switch + +### `PUT /spaces/{domain}` +Super Admin + +Update an existing `sip_domain`. + +JSON parameters: + +* `account_proxy_registrar_address`, **required**, the account proxy registrar address +* `account_realm`, **required**, the default realm for the accounts, fallback to the domain if not set +* `assistant_disable_qr_code` **required**, boolean +* `assistant_hide_create_account` **required**, boolean +* `assistant_hide_third_party_account` **required**, boolean +* `carddav_user_credentials` **required** boolean, enable credentials for CardDav servers +* `copyright_text` **required**, text, the copyright text +* `custom_provisioning_entries` **required**, text, the custom configuration used for the provisioning +* `custom_provisioning_overwrite_all` **required**, boolean, allow the custom configuration to overwrite the default one +* `custom_theme` **required**, boolean, allow a custom CSS file to be loaded +* `disable_broadcast_feature` **required**, boolean +* `disable_call_recordings_feature` **required**, boolean +* `disable_chat_feature` **required**, boolean +* `disable_meetings_feature` **required**, boolean +* `expire_at` **required**, date, the moment the space is expiring, set to `null` to never expire +* `hide_account_settings` **required**, boolean +* `hide_settings` **required**, boolean +* `intercom_features` **required**, boolean, the intercom features switch +* `intro_registration_text` **required**, Markdown text, the main registration page text +* `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 +* `name` **required**, the space name +* `newsletter_registration_address`, **required**, the newsletter registration email address +* `only_display_sip_uri_username` **required**, boolean +* `phone_registration` **required**, boolean, the phone registration switch +* `provisioning_use_linphone_provisioning_header` **required**, boolean +* `public_registration` **required**, boolean, the public registration switch +* `super` **required**, boolean, set the domain as a Super Domain +* `web_panel` **required**, boolean, the web panel switch + +### `DELETE /spaces/{domain}` +Super Admin + +Delete a domain, **be careful, all the related accounts will also be destroyed**. diff --git a/flexiapi/resources/views/api/documentation/spaces/carddav.blade.php b/flexiapi/resources/views/api/documentation/spaces/carddav.blade.php new file mode 100644 index 0000000..4d40c0d --- /dev/null +++ b/flexiapi/resources/views/api/documentation/spaces/carddav.blade.php @@ -0,0 +1,52 @@ +## Spaces CardDav Servers + +### `GET /spaces/{domain}/carddavs` +Super Admin + +List current CardDavs servers. + +### `GET /spaces/{domain}/carddavs/{id}` +Super Admin + +Get a specific CardDav server. + +### `POST /spaces/{domain}/carddavs` +Super Admin + +Create a CardDav server configuration. + +JSON parameters: + +* `uri` **required**, HTTP address of the server +* `port` **required**, integer, the port +* `enabled`, boolean +* `use_exact_match_policy`, boolean, whether match must be exact or approximate +* `min_characters`, integer, min characters to search +* `results_limit`, integer, limit the number of results, 0 to infinite +* `timeout`, integer, request timeout in seconds +* `delay`, integer, delay in milliseconds before submiting the request +* `fields_for_user_input`, comma separated list of vcard fields to match with user input +* `fields_for_domain`, comma separated list of vcard fields to match for SIP domain + +### `PUT /spaces/{domain}/carddavs/{id}` +Super Admin + +Update a CardDav server configuration. + +JSON parameters: + +* `uri` **required**, HTTP address of the server +* `port` **required**, integer, the port +* `enabled`, boolean +* `use_exact_match_policy`, boolean, whether match must be exact or approximate +* `min_characters`, integer, min characters to search +* `results_limit`, integer, limit the number of results, 0 to infinite +* `timeout`, integer, request timeout in seconds +* `delay`, integer, delay in milliseconds before submiting the request +* `fields_for_user_input`, comma separated list of vcard fields to match with user input +* `fields_for_domain`, comma separated list of vcard fields to match for SIP domain + +### `DELETE /spaces/{domain}/carddavs/{id}` +Super Admin + +Delete a specific CardDav server configuration. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/spaces/email.blade.php b/flexiapi/resources/views/api/documentation/spaces/email.blade.php new file mode 100644 index 0000000..584cf90 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/spaces/email.blade.php @@ -0,0 +1,26 @@ +## Spaces Email Server + +### `GET /spaces/{domain}/email` +Super Admin + +Get a space email server configuration + +### `POST /spaces/{domain}/email` +Super Admin + +Update an existing a space email server configuration. + +JSON parameters: + +* `host` **required**, the email server hostname +* `port` **required**, integer, the port +* `username`, the username +* `password`, the password +* `from_address`, email address, the sender email address +* `from_name`, the sender name +* `signature`, a text that will end every emails sent + +### `DELETE /spaces/{domain}/email` +Super Admin + +Delete the a space email server configuration. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation/statistics.blade.php b/flexiapi/resources/views/api/documentation/statistics.blade.php new file mode 100644 index 0000000..7e31f22 --- /dev/null +++ b/flexiapi/resources/views/api/documentation/statistics.blade.php @@ -0,0 +1,57 @@ +## Statistics + +FlexiAPI can record logs generated by the FlexiSIP server and compile them into statistics. + +### `POST /statistics/messages` +Admin + +Announce the creation of a message. + +JSON parameters: + +* `id` **required**, string +* `from` **required**, string the sender of the message +* `sent_at` **required**, string, format ISO8601, when the message was actually sent +* `encrypted` **required**, boolean +* `conference_id` string + +### `PATCH /statistics/messages/{message_id}/to/{to}/devices/{device_id}` +Admin + +Complete a message status. + +JSON parameters: + +* `last_status` **required**, an integer containing the last status code +* `received_at` **required**, format ISO8601, when the message was received + +### `POST /statistics/calls` +Admin + +Announce the beginning of a call. + +JSON parameters: + +* `id` **required**, string +* `from` **required**, string the initier of the call +* `to` **required**, string the destination of the call +* `initiated_at` **required**, string, format ISO8601, when the call was started +* `ended_at` string, format ISO8601, when the call finished +* `conference_id` string + +### `PATCH /statistics/calls/{call_id}/devices/{device_id}` +Admin + +Complete a call status. + +JSON parameters: + +* `rang_at` format ISO8601, when the device rang +* `invite_terminated` + * `at` format ISO8601, when the invitation ended + * `state` the termination state + +### `PATCH /statistics/calls/{call_id}` +Admin + +Update a call when ending. \ No newline at end of file diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index 6005b4c..d9ddb73 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -1,118 +1,4 @@ -# About & Auth. - -An API to deal with the Flexisip server. - -The API is available under `/api` - -A `content-type` and `accept` HTTP headers are REQUIRED to use the API properly - -``` -> GET /api/{endpoint} -> content-type: application/json -> accept: application/json -``` - -
- -Restricted endpoints are protected using a DIGEST authentication or an API Key mechanisms. - -### Access model - -The endpoints are accessible using three different models: - -- Public publicly accessible -- User the endpoint can only be accessed by an authenticated user -- Admin the endpoint can be only be accessed by an authenticated admin user -- Super Admin the endpoint can be only be accessed by an authenticated super admin user - -### Space expiration - -Super Admin 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. - -Currently supported languages: @php - echo implode(', ', config('app.authorized_locales')) -@endphp - -``` -> GET /api/{endpoint} -> Accept-Language: fr -> … - -< HTTP 422 -< { -< "message": "Le champ pseudo est obligatoire.", -< "errors": { -< "username": [ -< 0 => "Le champ pseudo est obligatoire." -< ] -< } -< } - -``` - -### Using the API Key - -You can retrieve an API Key from your account panel or using the dedicated API endpoint. - -**When generated by a User account the generated API Key will be restricted to the IP that generates it and will be destroyed if not used after some times.** - -**If you want a stable API Key, to integrate with another server component for example, you can generate one in the Administration Panel.** - -You can then use your freshly generated key by adding a new `x-api-key` header to your API requests: - -``` -> GET /api/{endpoint} -> x-api-key: {your-api-key} -> … -``` - -Or using a cookie: - -``` -> GET /api/{endpoint} -> Cookie: x-api-key={your-api-key} -> … -``` - -### Using a JWT token - -You can use a JWT token to authenticate on the API. - -To do so you MUST inject it as an `Authorization: Bearer` header and configure the API with the public key of the token emitter. - -``` -> GET /api/{endpoint} -> Authorization: Bearer {your-jwt-token} -> … -``` - -The API will then check if the token was signed properly, is still valid and authenticate a user that is actually available in the system. - -### Using DIGEST - -To discover the available hashing algorythm you MUST send an unauthenticated request to one of the restricted endpoints.
-Only DIGEST-MD5 and DIGEST-SHA-256 are supported through the authentication layer. - -A `from` (consisting of the user SIP address, prefixed with `sip:`) header is required to initiate the DIGEST flow. - -``` -> GET /api/{restricted-endpoint} -> from: sip:foobar@sip.example.org -> … - -< HTTP 401 -< content-type: application/json -< www-authenticate: Digest realm=test,qop=auth,algorithm=MD5,nonce="{nonce}",opaque="{opaque}" -< www-authenticate: Digest realm=test,qop=auth,algorithm=SHA-256,nonce="{nonce}",opaque="{opaque}" -``` - -You can find more documentation on the related [IETF RFC-7616](https://tools.ietf.org/html/rfc7616). - -
+@include('api.documentation.about_auth') # Endpoints @@ -123,709 +9,35 @@ You can find more documentation on the related [IETF RFC-7616](https://tools.iet Returns `pong` -## Spaces +@include('api.documentation.spaces') -Manage the list of allowed `spaces`. The admin accounts declared with a `domain` that is a `super` `sip_domain` will become Super Admin. +@include('api.documentation.spaces.carddav') -### `GET /spaces` -Super Admin +@include('api.documentation.spaces.email') -Get the list of declared Spaces. +@include('api.documentation.account_tokens') -### `GET /spaces/{domain}` -Super Admin +@include('api.documentation.accounts') -Get a Space. +@include('api.documentation.accounts.actions') -### `POST /spaces` -Super Admin +@include('api.documentation.accounts.carddav_credentials') -Create a new `space`. +@include('api.documentation.accounts.contacts_lists') -JSON parameters: +@include('api.documentation.accounts.contacts') -* `name` **required**, the space name -* `domain` **required**, the SIP domain to use, must be unique -* `host` **required**, the space host -* `super` 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` -* `disable_broadcast_feature` boolean, disable the broadcast feature, default to `true` -* `hide_settings` boolean, hide the app settings, default to `false` -* `hide_account_settings` boolean, disable the account settings, default to `false` -* `disable_call_recordings_feature` boolean, disable the call recording feature, default to `false` -* `only_display_sip_uri_username` boolean, hide the SIP uris in the app, default to `false` -* `assistant_hide_create_account` boolean, disable the account creation assistant, default to `false` -* `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) -* `copyright_text` text, the copyright text -* `intro_registration_text` Markdown text, the main registration page text -* `newsletter_registration_address`, the newsletter registration email address -* `account_proxy_registrar_address`, the account proxy registrar address -* `account_realm`, the default realm for the accounts, fallback to the domain if not set -* `custom_provisioning_entries` text, the custom configuration used for the provisioning -* `custom_provisioning_overwrite_all` boolean, allow the custom configuration to overwrite the default one -* `provisioning_use_linphone_provisioning_header` boolean -* `custom_theme` boolean, allow a custom CSS file to be loaded -* `web_panel` boolean, the web panel switch -* `public_registration` boolean, the public registration switch -* `phone_registration` boolean, the phone registration switch -* `intercom_features` boolean, the intercom features switch +@include('api.documentation.accounts.dictionary') -### `PUT /spaces/{domain}` -Super Admin +@include('api.documentation.accounts.email') -Update an existing `sip_domain`. +@include('api.documentation.accounts.external_account') -JSON parameters: +@include('api.documentation.accounts.phone') -* `name` **required**, the space name -* `super` **required**, boolean, set the domain as a Super Domain -* `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, 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 -* `copyright_text` **required**, text, the copyright text -* `intro_registration_text` **required**, Markdown text, the main registration page text -* `newsletter_registration_address`, **required**, the newsletter registration email address -* `account_proxy_registrar_address`, **required**, the account proxy registrar address -* `account_realm`, **required**, the default realm for the accounts, fallback to the domain if not set -* `custom_provisioning_entries` **required**, text, the custom configuration used for the provisioning -* `custom_provisioning_overwrite_all` **required**, boolean, allow the custom configuration to overwrite the default one -* `provisioning_use_linphone_provisioning_header` **required**, boolean -* `custom_theme` **required**, boolean, allow a custom CSS file to be loaded -* `web_panel` **required**, boolean, the web panel switch -* `public_registration` **required**, boolean, the public registration switch -* `phone_registration` **required**, boolean, the phone registration switch -* `intercom_features` **required**, boolean, the intercom features switch +@include('api.documentation.accounts.types') -### `DELETE /spaces/{domain}` -Super Admin - -Delete a domain, **be careful, all the related accounts will also be destroyed**. - -### `GET /spaces/{domain}/email` -Super Admin - -Get a space email server configuration - -### `POST /spaces/{domain}/email` -Super Admin - -Update an existing a space email server configuration. - -JSON parameters: - -* `host` **required**, the email server hostname -* `port` **required**, integer, the port -* `username`, the username -* `password`, the password -* `from_address`, email address, the sender email address -* `from_name`, the sender name -* `signature`, a text that will end every emails sent - -### `DELETE /spaces/{domain}/email` -Super Admin - -Delete the a space email server configuration. - -## Account Creation Request Tokens - -An `account_creation_request_token` is a unique token that can be validated and then used to generate a valid `account_creation_token`. - -### `POST /account_creation_request_tokens` -Public - -Create and return an `account_creation_request_token` that should then be validated to be used. - -## Account Creation Tokens - -An `account_creation_token` is a unique token that allow the creation or the validation of a unique account. - -### `POST /account_creation_tokens/send-by-push` -Public - -Create and send an `account_creation_token` using a push notification to the device. -Return `403` if a token was already sent, or if the tokens limit is reached for this device. -Return `503` if the token was not successfully sent. - -JSON parameters: - -* `pn_provider` **required**, the push notification provider, must be in apns.dev, apns or fcm -* `pn_param` the push notification parameter, can be null or contain only alphanumeric and underscore characters -* `pn_prid` the push notification unique id, can be null or contain only alphanumeric, dashes, underscore and colon characters - -### `POST /account_creation_tokens/using-account-creation-request-token` -Public - -Create an `account_creation_token` using an `account_creation_request_token`. -Return an `account_creation_token`. -Return `404` if the `account_creation_request_token` provided is not valid or expired otherwise. - -JSON parameters: - -* `account_creation_request_token` **required** - -### `POST /account_creation_tokens/consume` -User - -Consume an `account_creation_token` and link it to the authenticated account. -Return an `account_creation_token`. - -Return `404` if the `account_creation_token` provided is not valid. - -JSON parameters: - -* `account_creation_token` **required** - -### `POST /account_creation_tokens` -Admin - -Create and return an `account_creation_token`. - -## Account Recovery Tokens - -An `account_recovery_token` is a unique token that allow the recovery of an account. - -It can be used on the following page that also accepts a `phone` optional parameter to prefil the recovery form: - - {{ route('account.recovery.show.phone', ['account_recovery_token' => '_the_token_']) }} - {{ route('account.recovery.show.phone', ['account_recovery_token' => '_the_token_', 'phone' => '+3312341234']) }} - -### `POST /account_recovery_tokens/send-by-push` -Public - -Create and send an `account_recovery_token` using a push notification to the device. -Return `403` if a token was already sent, or if the tokens limit is reached for this device. -Return `503` if the token was not successfully sent. - -JSON parameters: - -* `pn_provider` **required**, the push notification provider, must be in apns.dev, apns or fcm -* `pn_param` the push notification parameter, can be null or contain only alphanumeric and underscore characters -* `pn_prid` the push notification unique id, can be null or contain only alphanumeric, dashes, underscore and colon characters - -## Auth Tokens - -### `POST /accounts/auth_token` -Public - -Generate an `auth_token`. To attach the generated token to an account see [`auth_token` attachement endpoint](#get-accountsauthtokenauthtokenattach). - -Return the `auth_token` object. - -### `GET /accounts/auth_token/{auth_token}/attach` -User - -Attach a publicly generated authentication token to the currently authenticated account. - -Return `404` if the token is non existing or invalid. - -## Accounts - -### `POST /accounts/with-account-creation-token` -Public - -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 -* `password` **required** minimum 6 characters -* `algorithm` **required**, values can be `SHA-256` or `MD5` -* `account_creation_token` the unique `account_creation_token` -* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` - -### `GET /accounts/{sip}/info` -Public - -Retrieve public information about the account. -Return `404` if the account doesn't exists. - -### `GET /accounts/me/api_key/{auth_token}` -Public - -Generate and retrieve a fresh API Key from an `auth_token`. The `auth_token` must be attached to an existing account, see [`auth_token` attachement endpoint](#get-accountsauthtokenauthtokenattach) to do so. - -Return `404` if the token is invalid or not attached. - -This endpoint is also setting the API Key as a Cookie. - -### `GET /accounts/me/api_key` -User - -Generate and retrieve a fresh API Key. -This endpoint is also setting the API Key as a Cookie. - -### `GET /accounts/me` -User - -Retrieve the account information. - -### `GET /accounts/me/services/turn` -User - -If configured, returns valid TURN credentials following the [draft-uberti-behave-turn-rest-00 IEFT Draft](https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00). - -### `GET /accounts/me/provision` -User - -Provision the account by generating a fresh `provisioning_token`. - -Return the account object. - -### `DELETE /accounts/me` -User - -Delete the account. - -### `POST /accounts/me/password` -User - -Change the account password. - -JSON parameters: - -* `algorithm` **required**, values can be `SHA-256` or `MD5` -* `old_password` **required** if the password is already set, the old password -* `password` **required**, the new password - -### `POST /accounts` -Admin - -To create an account directly from the API. - -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**, 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` -* `admin` optional, a boolean, set to `false` by default, create an admin account -* `phone` optional, a valid phone number, set a phone number to the account -* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` -* `dictionary` optional, an associative array attached to the account, see also the related endpoints. - -### `PUT /accounts/{id}` -Admin - -Update an existing account. Ensure to resend all the parameters to not reset them. - -JSON parameters: - -* `username` unique username, minimum 6 characters -* `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 -* `email` optional, must be an email, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` -* `admin` optional, a boolean, set to `false` by default -* `phone` optional, a valid phone number, set a phone number to the account -* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` - -Using this endpoint you can also set a fresh dictionnary if the parameter is set. The existing dictionary entries will be destroyed. - -* `dictionary` optional, an associative array attached to the account, see also the related endpoints. - -This endpoint also return the current `phone_change_code` and `email_change_code` if they are available. - -### `GET /accounts` -Admin - -Retrieve all the accounts, paginated. - -### `GET /accounts/{id}` -Admin - -Retrieve a specific account. - -### `GET /accounts/{sip}/search` -Admin - -Search for a specific account by sip address. - -### `GET /accounts/{email}/search-by-email` -Admin - -Search for a specific account by email. - -### `DELETE /accounts/{id}` -Admin - -Delete a specific account and its related information. - -### `POST /accounts/{id}/activate` -Admin - -Activate an account. - -### `POST /accounts/{id}/deactivate` -Admin - -Deactivate an account. - -### `POST /accounts/{id}/block` -Admin - -Block an account. - -### `POST /accounts/{id}/unblock` -Admin - -Unblock an account. - -### `GET /accounts/{id}/provision` -Admin - -Provision an account by generating a fresh `provisioning_token`. - -### `POST /accounts/{id}/send_provisioning_email` -Admin - -Send a provisioning email to the account. - -### `POST /accounts/{id}/send_reset_password_email` -Admin - -Send a password reset email to the account. - -## Accounts email - -### `POST /accounts/me/email/request` -User - -Request to change the account email. An email will be sent to the new email address to confirm the operation. - -Will return `403` if the account doesn't have a validated Account Creation Token attached to it. - -JSON parameters: - -* `email` the new email address, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` - -### `POST /accounts/me/email` -User - -Confirm the code received and change the email. -Activate the account. - -JSON parameters: - -* `code` the code received by email - -Return the updated account. - -## Accounts phone number - -### `POST /accounts/me/phone/request` -User - -Request a specific code by SMS to change the phone number. - -Will return `403` if the account doesn't have a validated Account Creation Token attached to it. - -JSON parameters: - -* `phone` the phone number to send the SMS - -### `POST /accounts/me/phone` -User - -Confirm the code received and change the phone number. -Activate the account. - -JSON parameters: - -* `code` the received SMS code - -Return the updated account. - -## Accounts devices - -### `GET /accounts/{id/me}/devices` -Admin -User - -Return the user registered devices. - -### `DELETE /accounts/{id/me}/devices/{uuid}` -Admin -User - -Remove one of the user registered devices. - -## Account contacts - -### `GET /accounts/me/contacts` -User - -Return the user contacts. - -### `GET /accounts/me/contacts/{sip}` -User - -Return a user contact. - -## vCards storage - -### `POST /accounts/{id/me}/vcards-storage` -Admin -User - -Store a vCard. - -JSON parameters: - -* `vcard`, mandatory, a valid vCard having a mandatory `UID` parameter that is uniquelly identifying it. This `UID` parameter will then be used to manipulate the vcard through the following endpoints as `uuid`. - -### `PUT /accounts/{id/me}/vcards-storage/{uuid}` -Admin -User - -Update a vCard. - -JSON parameters: - -* `vcard`, mandatory, a valid vCard having a mandatory `UID` parameter that is uniquelly identifying it and is the same as the `uuid` parameter. - -### `GET /accounts/{id/me}/vcards-storage` -Admin -User - -Return the list of stored vCards - -### `GET /accounts/{id/me}/vcards-storage/{uuid}` -Admin -User - -Return a stored vCard - -### `DELETE /accounts/{id/me}/vcards-storage/{uuid}` -Admin -User - -Delete a stored vCard - -## Contacts - -### `GET /accounts/{id}/contacts` -Admin - -Get all the account contacts. - -### `POST /accounts/{id}/contacts/{contact_id}` -Admin - -Add a contact to the list. - -### `DELETE /accounts/{id}/contacts/{contact_id}` -Admin - -Remove a contact from the list. - -## Dictionary - -### `GET /accounts/{id}/dictionary` -Admin - -Get all the account dictionary entries. - -### `POST /accounts/{id}/dictionary/{key}` -Admin - -Add or update a new entry to the dictionary - -JSON parameters: - -* `value` **required**, the entry value - -## External Account - -### `GET /accounts/{id}/external` -Admin - -Get the external account. - -### `POST /accounts/{id}/external` -Admin - -Create or update the external account. - -JSON parameters: - -* `username` **required** -* `domain` **required** -* `password` **required** -* `realm` must be different than `domain` -* `registrar` must be different than `domain` -* `outbound_proxy` must be different than `domain` -* `protocol` **required**, must be `UDP`, `TCP` or `TLS` - -### `DELETE /accounts/{id}/external` -Admin - -Delete the external account. - -## Account Actions - -The following endpoints will return `403 Forbidden` if the requested account doesn't have a DTMF protocol configured. - -### `GET /accounts/{id}/actions` -Admin - -Show an account related actions. - -### `GET /accounts/{id}/actions/{action_id}` -Admin - -Show an account related action. - -### `POST /accounts/{id}/actions/` -Admin - -Create an account action. - -JSON parameters: - -* `key` **required**, alpha numeric with dashes, lowercase -* `code` **required**, alpha numeric, lowercase - -### `PUT /accounts/{id}/actions/{action_id}` -Admin - -Create an account action. - -JSON parameters: - -* `key` **required**, alpha numeric with dashes, lowercase -* `code` **required**, alpha numeric, lowercase - -### `DELETE /accounts/{id}/actions/{action_id}` -Admin - -Delete an account related action. - -## Contacts Lists - -### `GET /contacts_lists` -Admin - -Show all the contacts lists. - -### `GET /contacts_lists/{id}` -Admin - -Show a contacts list. - -### `POST /contacts_lists` -Admin - -Create a contacts list. - -JSON parameters: - -* `title` **required** -* `description` **required** - -### `PUT /contacts_lists/{id}` -Admin - -Update a contacts list. - -JSON parameters: - -* `title` **required** -* `description` **required** - -### `DELETE /contacts_lists/{id}` -Admin - -Delete a contacts list. - -### `POST /contacts_lists/{contacts_list_id}/contacts/{contact_id}` -Admin - -Add a contact to the contacts list. - -### `DELETE /contacts_lists/{contacts_list_id}/contacts/{contact_id}` -Admin - -Remove a contact from the contacts list. - -### `POST /accounts/{id}/contacts_lists/{contacts_list_id}` -Admin - -Add a contacts list to the account. - -### `DELETE /accounts/{id}/contacts_lists/{contacts_list_id}` -Admin - -Remove a contacts list from the account. - -## Account Types - -### `GET /account_types` -Admin - -Show all the account types. - -### `GET /account_types/{id}` -Admin - -Show an account type. - -### `POST /account_types` -Admin - -Create an account type. - -JSON parameters: - -* `key` **required**, alpha numeric with dashes, lowercase - -### `PUT /account_types/{id}` -Admin - -Update an account type. - -JSON parameters: - -* `key` **required**, alpha numeric with dashes, lowercase - -### `DELETE /account_types/{id}` -Admin - -Delete an account type. - -### `POST /accounts/{id}/types/{type_id}` -Admin - -Add a type to the account. - -### `DELETE /accounts/{id}/contacts/{type_id}` -Admin - -Remove a type from the account. +@include('api.documentation.accounts.vcards_storage') ## Messages @@ -865,63 +77,8 @@ Return the list of Phone Countries and their current status. If a country is deactivated all the new submitted phones submitted on the platform will be blocked. -## Statistics -FlexiAPI can record logs generated by the FlexiSIP server and compile them into statistics. - -### `POST /statistics/messages` -Admin - -Announce the creation of a message. - -JSON parameters: - -* `id` **required**, string -* `from` **required**, string the sender of the message -* `sent_at` **required**, string, format ISO8601, when the message was actually sent -* `encrypted` **required**, boolean -* `conference_id` string - -### `PATCH /statistics/messages/{message_id}/to/{to}/devices/{device_id}` -Admin - -Complete a message status. - -JSON parameters: - -* `last_status` **required**, an integer containing the last status code -* `received_at` **required**, format ISO8601, when the message was received - -### `POST /statistics/calls` -Admin - -Announce the beginning of a call. - -JSON parameters: - -* `id` **required**, string -* `from` **required**, string the initier of the call -* `to` **required**, string the destination of the call -* `initiated_at` **required**, string, format ISO8601, when the call was started -* `ended_at` string, format ISO8601, when the call finished -* `conference_id` string - -### `PATCH /statistics/calls/{call_id}/devices/{device_id}` -Admin - -Complete a call status. - -JSON parameters: - -* `rang_at` format ISO8601, when the device rang -* `invite_terminated` - * `at` format ISO8601, when the invitation ended - * `state` the termination state - -### `PATCH /statistics/calls/{call_id}` -Admin - -Update a call when ending. +@include('api.documentation.statistics') JSON parameters: diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php index 91e3c7f..5a988ee 100644 --- a/flexiapi/routes/api.php +++ b/flexiapi/routes/api.php @@ -18,18 +18,20 @@ */ use App\Http\Controllers\Api\Account\VcardsStorageController; -use App\Http\Controllers\Api\Admin\AccountActionController; -use App\Http\Controllers\Api\Admin\AccountContactController; +use App\Http\Controllers\Api\Admin\Account\ActionController; +use App\Http\Controllers\Api\Admin\Account\CardDavCredentialsController; +use App\Http\Controllers\Api\Admin\Account\ContactController; +use App\Http\Controllers\Api\Admin\Account\DictionaryController; +use App\Http\Controllers\Api\Admin\Account\TypeController; 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\ExternalAccountController; +use App\Http\Controllers\Api\Admin\Space\CardDavServerController; +use App\Http\Controllers\Api\Admin\Space\EmailServerController; use App\Http\Controllers\Api\Admin\SpaceController; -use App\Http\Controllers\Api\Admin\EmailServerController; use App\Http\Controllers\Api\Admin\VcardsStorageController as AdminVcardsStorageController; -use App\Http\Controllers\Api\StatisticsMessageController; use App\Http\Controllers\Api\StatisticsCallController; +use App\Http\Controllers\Api\StatisticsMessageController; use Illuminate\Http\Request; Route::get('/', 'Api\ApiController@documentation')->name('api'); @@ -93,23 +95,19 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo // Super admin Route::group(['middleware' => ['auth.super_admin']], function () { - Route::prefix('spaces')->controller(SpaceController::class)->group(function () { - Route::get('/', 'index'); - Route::get('{domain}', 'show'); - Route::post('/', 'store'); - Route::put('{domain}', 'update'); - Route::delete('{domain}', 'destroy'); - }); + Route::apiResource('spaces', SpaceController::class); Route::prefix('spaces/{domain}/email')->controller(EmailServerController::class)->group(function () { Route::get('/', 'show'); Route::post('/', 'store'); Route::delete('/', 'destroy'); }); + + Route::apiResource('spaces/{domain}/carddavs', CardDavServerController::class); }); // Account creation token - Route::post('account_creation_tokens', 'Api\Admin\AccountCreationTokenController@create'); + Route::post('account_creation_tokens', 'Api\Admin\Account\CreationTokenController@create'); // Accounts Route::prefix('accounts')->controller(AdminAccountController::class)->group(function () { @@ -140,15 +138,19 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo }); // Account contacts - Route::prefix('accounts/{id}/contacts')->controller(AccountContactController::class)->group(function () { + Route::prefix('accounts/{id}/contacts')->controller(ContactController::class)->group(function () { Route::get('/', 'index'); Route::get('{contact_id}', 'show'); Route::post('{contact_id}', 'add'); Route::delete('{contact_id}', 'remove'); }); - Route::apiResource('accounts/{id}/actions', AccountActionController::class); - Route::apiResource('account_types', AccountTypeController::class); + Route::group(['middleware' => ['feature.carddav_user_credentials']], function () { + Route::apiResource('accounts/{id}/carddavs', CardDavCredentialsController::class, ['only' => ['index', 'show', 'update', 'destroy']]); + }); + + Route::apiResource('accounts/{id}/actions', ActionController::class); + Route::apiResource('account_types', TypeController::class); Route::apiResource('accounts/{account_id}/vcards-storage', AdminVcardsStorageController::class); Route::apiResource('contacts_lists', ContactsListController::class); @@ -157,7 +159,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo Route::delete('{id}/contacts/{contacts_id}', 'contactRemove'); }); - Route::prefix('accounts/{id}/dictionary')->controller(AccountDictionaryController::class)->group(function () { + Route::prefix('accounts/{id}/dictionary')->controller(DictionaryController::class)->group(function () { Route::get('/', 'index'); Route::get('{key}', 'show'); Route::post('{key}', 'set'); diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 37076c6..88b29e3 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -26,16 +26,17 @@ use App\Http\Controllers\Account\PasswordController; use App\Http\Controllers\Account\PhoneController; use App\Http\Controllers\Account\ProvisioningController; use App\Http\Controllers\Account\RecoveryController; -use App\Http\Controllers\Admin\AccountAccountTypeController; -use App\Http\Controllers\Admin\AccountActionController; -use App\Http\Controllers\Admin\AccountActivityController; -use App\Http\Controllers\Admin\AccountContactController; use App\Http\Controllers\Admin\AccountController as AdminAccountController; -use App\Http\Controllers\Admin\AccountDeviceController; -use App\Http\Controllers\Admin\AccountDictionaryController; -use App\Http\Controllers\Admin\AccountImportController; -use App\Http\Controllers\Admin\AccountStatisticsController; -use App\Http\Controllers\Admin\AccountTypeController; +use App\Http\Controllers\Admin\Account\AccountTypeController; +use App\Http\Controllers\Admin\Account\ActionController; +use App\Http\Controllers\Admin\Account\ActivityController; +use App\Http\Controllers\Admin\Account\CardDavCredentialsController; +use App\Http\Controllers\Admin\Account\ContactController; +use App\Http\Controllers\Admin\Account\DeviceController as AdminAccountDeviceController; +use App\Http\Controllers\Admin\Account\DictionaryController; +use App\Http\Controllers\Admin\Account\ImportController; +use App\Http\Controllers\Admin\Account\StatisticsController as AdminAccountStatisticsController; +use App\Http\Controllers\Admin\Account\TypeController; use App\Http\Controllers\Admin\ApiKeyController as AdminApiKeyController; use App\Http\Controllers\Admin\ContactsListContactController; use App\Http\Controllers\Admin\ContactsListController; @@ -44,6 +45,7 @@ use App\Http\Controllers\Admin\PhoneCountryController; use App\Http\Controllers\Admin\ProvisioningEmailController; use App\Http\Controllers\Admin\ResetPasswordEmailController; use App\Http\Controllers\Admin\Space\EmailServerController; +use App\Http\Controllers\Admin\Space\CardDavServerController; use App\Http\Controllers\Admin\SpaceController; use App\Http\Controllers\Admin\StatisticsController; use Illuminate\Support\Facades\Route; @@ -51,7 +53,7 @@ use Illuminate\Support\Facades\Route; Route::redirect('/', 'login')->name('account.home'); Route::get('about', 'AboutController@about')->name('about'); -Route::middleware(['web_panel_enabled'])->group(function () { +Route::middleware(['feature.web_panel_enabled'])->group(function () { Route::get('wizard/{provisioning_token}', 'Account\ProvisioningController@wizard')->name('provisioning.wizard'); Route::get('login', 'Account\AuthenticateController@login')->name('account.login'); @@ -88,11 +90,11 @@ Route::name('provisioning.')->prefix('provisioning')->controller(ProvisioningCon Route::get('/', 'show')->name('show'); }); -Route::middleware(['web_panel_enabled'])->group(function () { - Route::middleware(['public_registration'])->group(function () { +Route::middleware(['feature.web_panel_enabled'])->group(function () { + Route::middleware(['feature.public_registration'])->group(function () { Route::redirect('register', 'register/email')->name('account.register'); - Route::middleware(['phone_registration'])->group(function () { + Route::middleware(['feature.phone_registration'])->group(function () { Route::get('register/phone', 'Account\RegisterController@registerPhone')->name('account.register.phone'); }); @@ -117,7 +119,7 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::post('/', 'store')->name('email.update'); }); - Route::middleware(['phone_registration'])->group(function () { + Route::middleware(['feature.phone_registration'])->group(function () { Route::prefix('phone')->controller(PhoneController::class)->group(function () { Route::get('change', 'change')->name('phone.change'); Route::post('change', 'requestChange')->name('phone.request_change'); @@ -169,6 +171,8 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::get('delete', 'delete')->name('delete'); Route::delete('/', 'destroy')->name('destroy'); }); + Route::resource('{space}/carddavs', CardDavServerController::class, ['except' => ['index', 'show']]); + Route::get('{space}/carddavs/{carddav}/delete', 'Admin\Space\CardDavServerController@delete')->name('carddavs.delete'); }); Route::name('api_keys.')->prefix('api_keys')->controller(AdminApiKeyController::class)->group(function () { @@ -205,14 +209,14 @@ Route::middleware(['web_panel_enabled'])->group(function () { }); Route::name('account.')->prefix('accounts')->group(function () { - Route::name('import.')->prefix('import')->controller(AccountImportController::class)->group(function () { + Route::name('import.')->prefix('import')->controller(ImportController::class)->group(function () { Route::get('/', 'create')->name('create'); Route::post('/', 'store')->name('store'); Route::post('handle', 'handle')->name('handle'); }); - Route::middleware(['intercom_features'])->group(function () { - Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () { + Route::middleware(['feature.intercom'])->group(function () { + Route::name('type.')->prefix('types')->controller(TypeController::class)->group(function () { Route::get('/', 'index')->name('index'); Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); @@ -222,13 +226,13 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::delete('{type_id}', 'destroy')->name('destroy'); }); - Route::name('account_type.')->prefix('{account}/types')->controller(AccountAccountTypeController::class)->group(function () { + Route::name('account_type.')->prefix('{account}/types')->controller(AccountTypeController::class)->group(function () { Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); Route::delete('{type_id}', 'destroy')->name('destroy'); }); - Route::name('action.')->prefix('{account}/actions')->controller(AccountActionController::class)->group(function () { + Route::name('action.')->prefix('{account}/actions')->controller(ActionController::class)->group(function () { Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); Route::get('{action_id}/edit', 'edit')->name('edit'); @@ -268,7 +272,7 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::get('send', 'send')->name('send'); }); - Route::name('contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () { + Route::name('contact.')->prefix('{account}/contacts')->controller(ContactController::class)->group(function () { Route::get('/', 'index')->name('index'); Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); @@ -276,12 +280,15 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::delete('/', 'destroy')->name('destroy'); }); - Route::name('device.')->prefix('{account}/devices')->controller(AccountDeviceController::class)->group(function () { + Route::name('device.')->prefix('{account}/devices')->controller(AdminAccountDeviceController::class)->group(function () { Route::get('{device_id}/delete', 'delete')->name('delete'); Route::delete('/', 'destroy')->name('destroy'); }); - Route::name('dictionary.')->prefix('{account}/dictionary')->controller(AccountDictionaryController::class)->group(function () { + Route::resource('{account}/carddavs', CardDavCredentialsController::class, ['only' => ['create', 'store', 'destroy']]); + Route::get('{account}/carddavs/{carddav}/delete', 'Admin\Account\CardDavCredentialsController@delete')->name('carddavs.delete'); + + Route::name('dictionary.')->prefix('{account}/dictionary')->controller(DictionaryController::class)->group(function () { Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); Route::get('{entry}/edit', 'edit')->name('edit'); @@ -297,11 +304,11 @@ Route::middleware(['web_panel_enabled'])->group(function () { Route::delete('/', 'destroy')->name('destroy'); }); - Route::name('activity.')->prefix('{account}/activity')->controller(AccountActivityController::class)->group(function () { + Route::name('activity.')->prefix('{account}/activity')->controller(ActivityController::class)->group(function () { Route::get('/', 'index')->name('index'); }); - Route::name('statistics.')->prefix('{account}/statistics')->controller(AccountStatisticsController::class)->group(function () { + Route::name('statistics.')->prefix('{account}/statistics')->controller(AdminAccountStatisticsController::class)->group(function () { Route::get('/', 'show')->name('show'); Route::post('call_logs', 'editCallLogs')->name('edit_call_logs'); Route::get('call_logs', 'showCallLogs')->name('show_call_logs'); diff --git a/flexiapi/tests/Feature/AccountProvisioningTest.php b/flexiapi/tests/Feature/AccountProvisioningTest.php index a03f68e..b328754 100644 --- a/flexiapi/tests/Feature/AccountProvisioningTest.php +++ b/flexiapi/tests/Feature/AccountProvisioningTest.php @@ -23,6 +23,7 @@ use App\Account; use App\AuthToken; use App\Password; use App\ProvisioningToken; +use App\SpaceCardDavServer; use App\Space; use Illuminate\Support\Facades\DB; use Tests\TestCase; @@ -218,6 +219,58 @@ class AccountProvisioningTest extends TestCase $this->assertNotEquals($password->account->passwords()->first()->password, $currentPassword); } + public function testCardDavProvisioning() + { + $account = Account::factory()->create(); + $account->generateUserApiKey(); + + $admin = Account::factory()->superAdmin()->create(); + $admin->generateUserApiKey(); + + $this->keyAuthenticated($admin) + ->json('POST', '/api/spaces/' . $admin->space->host . '/carddavs', [ + 'uri' => 'http://uri.com' + ]) + ->assertStatus(200); + + $this->withHeaders([ + 'x-linphone-provisioning' => true, + ]) + ->keyAuthenticated($account) + ->get($this->route . '/me') + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee('remote_contact_directory_0') + ->assertSee('carddav_fields_for_user_input'); + + Space::where('domain', $admin->domain)->update(['carddav_user_credentials' => true]); + $cardDavServer = SpaceCardDavServer::first(); + + $credentials = [ + 'username' => 'john', + 'domain' => 'hop.com', + 'password' => '1234', + 'algorithm' => 'MD5' + ]; + + $this->keyAuthenticated($admin) + ->json('PUT', '/api/accounts/' . $account->id . '/carddavs/' . $cardDavServer->id , $credentials) + ->assertStatus(200); + + $this->withHeaders([ + 'x-linphone-provisioning' => true, + ]) + ->keyAuthenticated($account) + ->get($this->route . '/me') + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee('remote_contact_directory_0') + ->assertSee('auth_info_0') + ->assertSee('ce44af78c5f81f5870818b8db523fd95') + ->assertSee($credentials['username']) + ->assertSee($credentials['algorithm']); + } + public function testConfirmationKeyProvisioning() { $response = $this->withHeaders([ diff --git a/flexiapi/tests/Feature/ApiSpaceCardDavServersTest.php b/flexiapi/tests/Feature/ApiSpaceCardDavServersTest.php new file mode 100644 index 0000000..125e95a --- /dev/null +++ b/flexiapi/tests/Feature/ApiSpaceCardDavServersTest.php @@ -0,0 +1,249 @@ +. +*/ + +namespace Tests\Feature; + +use App\Account; +use App\Space; +use App\SpaceCardDavServer; +use Carbon\Carbon; +use Tests\TestCase; + +class ApiSpaceCardDavServersTest extends TestCase +{ + protected $spaceRoute = '/api/spaces'; + protected $accountRoute = '/api/accounts'; + + public function testCardDavServerAdminForbidden() + { + $admin = Account::factory()->admin()->create(); + $admin->generateUserApiKey(); + + $route = $this->spaceRoute . '/' . $admin->space->host . '/carddavs'; + $uri = 'http://test.com'; + + $this->keyAuthenticated($admin) + ->json('POST', $route, [ + 'uri' => $uri + ]) + ->assertStatus(403); + } + + public function testCardDavServerCrud() + { + $superAdmin = Account::factory()->superAdmin()->create(); + $superAdmin->generateUserApiKey(); + + $route = $this->spaceRoute . '/' . $superAdmin->space->host . '/carddavs'; + $uri = 'http://test.com'; + $uri2 = 'http://test2.com'; + + $this->keyAuthenticated($superAdmin) + ->json('POST', $route, [ + 'uri' => $uri + ]) + ->assertStatus(200); + + $this->keyAuthenticated($superAdmin) + ->json('GET', $route) + ->assertJsonFragment([ + 'uri' => $uri, + 'enabled' => false + ]) + ->assertStatus(200); + + $cardDavServer = SpaceCardDavServer::first(); + + $this->keyAuthenticated($superAdmin) + ->json('GET', $route . '/' . $cardDavServer->id) + ->assertJsonFragment([ + 'uri' => $uri, + 'enabled' => false + ]) + ->assertStatus(200); + + $this->keyAuthenticated($superAdmin) + ->json('PUT', $route. '/' . $cardDavServer->id, [ + 'uri' => $uri2, + 'enabled' => true + ]) + ->assertStatus(200); + + $this->keyAuthenticated($superAdmin) + ->json('PUT', $route. '/' . $cardDavServer->id, [ + 'uri' => $uri2, + 'enabled' => true, + 'fields_for_domain' => 'wrong _ data' + ])->assertJsonValidationErrors(['fields_for_domain']); + + $this->keyAuthenticated($superAdmin) + ->json('GET', $route . '/' . $cardDavServer->id) + ->assertJsonFragment([ + 'uri' => $uri2, + 'enabled' => true + ]) + ->assertStatus(200); + + $this->keyAuthenticated($superAdmin) + ->json('DELETE', $route . '/' . $cardDavServer->id) + ->assertStatus(200); + + $this->keyAuthenticated($superAdmin) + ->json('GET', $route . '/' . $cardDavServer->id) + ->assertStatus(404); + + $this->keyAuthenticated($superAdmin) + ->json('GET', $route) + ->assertJson([]) + ->assertStatus(200); + } + + public function testCardDavServerOtherAdminForbidden() + { + $admin = Account::factory()->admin()->create(); + $admin->generateUserApiKey(); + + $secondSpace = Space::factory()->secondDomain()->create(); + $secondAdmin = Account::factory()->admin()->fromSpace($secondSpace)->create(); + $secondAdmin->generateUserApiKey(); + + $superAdmin = Account::factory()->superAdmin()->create(); + $superAdmin->generateUserApiKey(); + + $credentials = [ + 'username' => 'john', + 'domain' => 'hop.com', + 'password' => '1234', + 'algorithm' => 'MD5' + ]; + + $route = $this->spaceRoute . '/' . $admin->space->host . '/carddavs'; + + // Creating the CardDav + $this->keyAuthenticated($superAdmin) + ->json('POST', $route, [ + 'uri' => 'http://server', + ]) + ->assertStatus(200); + + // Allowing CardDav credentials for Admin 1 space + Space::where('domain', $admin->domain)->update(['carddav_user_credentials' => true]); + + // First Admin can get its own credentials + $this->keyAuthenticated($admin) + ->json('GET', $this->accountRoute . '/' . $admin->id . '/carddavs') + ->assertStatus(200); + + // The other Admin cannot get it + $this->keyAuthenticated($secondAdmin) + ->json('GET', $this->accountRoute . '/' . $admin->id . '/carddavs') + ->assertStatus(403); + } + + public function testCardDavCredentials() + { + $admin = Account::factory()->admin()->create(); + $admin->generateUserApiKey(); + + $user = Account::factory()->create(); + $user->generateUserApiKey(); + + $route = $this->accountRoute . '/' . $user->id . '/carddavs'; + + $this->keyAuthenticated($admin) + ->json('GET', $route) + ->assertStatus(403); + + Space::where('domain', $user->domain)->update(['carddav_user_credentials' => true]); + + $this->keyAuthenticated($admin) + ->json('GET', $route) + ->assertSee('{}') + ->assertStatus(200); + + // Create the CardDav server + + Space::where('domain', $user->domain)->update(['super' => true]); + + $this->keyAuthenticated($admin) + ->json('POST', $this->spaceRoute . '/' . $admin->space->host . '/carddavs', [ + 'uri' => 'http://uri.com' + ]) + ->assertStatus(200); + + Space::where('domain', $user->domain)->update(['super' => false]); + + $cardDavServer = SpaceCardDavServer::first(); + + // Create the credentials + + $credentials = [ + 'username' => 'john', + 'domain' => 'hop.com', + 'password' => '1234', + 'algorithm' => 'MD5' + ]; + + $this->keyAuthenticated($admin) + ->json('PUT', $route . '/431' , $credentials) + ->assertStatus(404); + + $this->keyAuthenticated($admin) + ->json('PUT', $route . '/' . $cardDavServer->id , $credentials) + ->assertStatus(200); + + $credentials['domain'] = 'hop2.com'; + + // Again + $this->keyAuthenticated($admin) + ->json('PUT', $route . '/' . $cardDavServer->id , $credentials) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('GET', $route) + //->dump() + ->assertJsonFragment([ + 'username' => $credentials['username'], + ]) + ->assertJsonFragment([ + 'domain' => $credentials['domain'], + ]) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('GET', $route . '/' . $cardDavServer->id) + ->assertJsonFragment([ + 'username' => $credentials['username'], + ]) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('DELETE', $route . '/' . $cardDavServer->id) + ->assertStatus(200); + + $this->keyAuthenticated($admin) + ->json('GET', $route . '/' . $cardDavServer->id) + ->assertStatus(404); + + $this->keyAuthenticated($admin) + ->json('GET', $route) + ->assertSee('{}') + ->assertStatus(200); + } +} diff --git a/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php b/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php index 3090357..e159a5d 100644 --- a/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php +++ b/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php @@ -64,7 +64,7 @@ class ApiSpaceWithMiddlewareTest extends TestCase ->json('PUT', $this->route . '/' . $admin->domain, $space) ->assertStatus(200); - $this->keyAuthenticated($admin) + $this->keyAuthenticated($superAdmin) ->json($this->method, $this->accountRoute, [ 'username' => 'new', 'algorithm' => 'SHA-256',