From 724e4c4e5b77a7ee25891edc24298e1d8e553c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Wed, 11 Jun 2025 15:36:51 +0000 Subject: [PATCH] Fix/325 328 --- CHANGELOG.md | 8 ++++ .../Controllers/Admin/SpaceController.php | 8 +++- .../Api/Admin/AccountController.php | 37 ++++++++++++++++ .../Middleware/AuthenticateDigestOrKey.php | 11 ++--- .../database/factories/PasswordFactory.php | 6 +-- flexiapi/lang/fr.json | 2 +- .../views/admin/space/configuration.blade.php | 2 +- .../views/admin/space/create.blade.php | 8 ++++ .../views/admin/space/edit.blade.php | 2 +- .../api/documentation_markdown.blade.php | 10 +++++ flexiapi/routes/api.php | 2 + flexiapi/tests/Feature/ApiAccountTest.php | 43 +++++++++++++++++++ 12 files changed, 124 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 770575d..cc037b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,14 @@ v2.0 - Fix FLEXIAPI-224 Add a console script to send Space Expiration emails - Fix FLEXIAPI-297 Fix PrId and CallId validations - Fix FLEXIAPI-305 Add specific error page for Space Expiration +- Fix FLEXIAPI-169 Added missing selinux label to log files and storage directory +- Fix FLEXIAPI-313 Fix the admin device deletion link, recover the missing... +- Fix FLEXIAPI-318 Fix email recovery validation +- Fix FLEXIAPI-319 Fix the admin device deletion link, recover the missing method +- Fix FLEXIAPI-321 Disable the account creation button when the Space is full for admins +- Fix FLEXIAPI-322 Api Keys documentation +- Fix FLEXIAPI-328 Set realm on Space creation, limit the update if some accounts are present +- Fix FLEXIAPI-325 Add endpoints to send the password reset and provisioning emails v1.6 ---- diff --git a/flexiapi/app/Http/Controllers/Admin/SpaceController.php b/flexiapi/app/Http/Controllers/Admin/SpaceController.php index 47f7881..3dde6d7 100644 --- a/flexiapi/app/Http/Controllers/Admin/SpaceController.php +++ b/flexiapi/app/Http/Controllers/Admin/SpaceController.php @@ -67,12 +67,14 @@ class SpaceController extends Controller 'domain' => ['required', 'unique:spaces', new Domain()], 'host' => 'nullable|regex:/'. Space::HOST_REGEX . '/', 'full_host' => ['required', 'unique:spaces,host', new Domain()], + 'account_realm' => [new Domain()], ]); $space = new Space(); $space->name = $request->get('name'); $space->domain = $request->get('domain'); $space->host = $request->get('full_host'); + $space->account_realm = $request->get('account_realm'); $space->save(); return redirect()->route('admin.spaces.index'); @@ -163,7 +165,11 @@ class SpaceController extends Controller $space->confirmed_registration_text = $request->get('confirmed_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'); + + if ($space->accounts()->count() == 0) { + $space->account_realm = $request->get('account_realm'); + } + $space->custom_provisioning_entries = $request->get('custom_provisioning_entries'); $space->custom_provisioning_overwrite_all = getRequestBoolean($request, 'custom_provisioning_overwrite_all'); $space->provisioning_use_linphone_provisioning_header = getRequestBoolean($request, 'provisioning_use_linphone_provisioning_header'); diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php index 65afd37..f2b6482 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php @@ -21,14 +21,19 @@ namespace App\Http\Controllers\Api\Admin; use App\Http\Controllers\Controller; use Illuminate\Http\Request; +use Illuminate\Support\Str; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; use App\Account; use App\AccountTombstone; use App\AccountType; use App\ContactsList; +use App\ResetPasswordEmailToken; use App\Http\Requests\Account\Create\Api\AsAdminRequest; use App\Http\Requests\Account\Update\Api\AsAdminRequest as ApiAsAdminRequest; +use App\Mail\Provisioning; +use App\Mail\ResetPassword; use App\Services\AccountService; use App\Space; @@ -198,4 +203,36 @@ class AccountController extends Controller return Account::findOrFail($accountId)->contactsLists()->detach($contactsListId); } + + /** + * Emails + */ + + public function sendProvisioningEmail(int $accountId) + { + $account = Account::findOrFail($accountId); + + if (!$account->email) abort(403, 'No email configured'); + + $account->provision(); + + Mail::to($account)->send(new Provisioning($account)); + + Log::channel('events')->info('API: Sending provisioning email', ['id' => $account->identifier]); + } + + public function sendResetPasswordEmail(int $accountId) + { + $account = Account::findOrFail($accountId); + + if (!$account->email) abort(403, 'No email configured'); + + $resetPasswordEmail = new ResetPasswordEmailToken; + $resetPasswordEmail->account_id = $account->id; + $resetPasswordEmail->token = Str::random(16); + $resetPasswordEmail->email = $account->email; + $resetPasswordEmail->save(); + + Mail::to($account)->send(new ResetPassword($account)); + } } diff --git a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php index dea0f93..595c527 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php +++ b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php @@ -70,8 +70,6 @@ class AuthenticateDigestOrKey ->where('domain', $domain) ->firstOrFail(); - $resolvedRealm = space()?->account_realm ?? $domain; - // DIGEST authentication if ($request->header('Authorization')) { @@ -95,7 +93,7 @@ class AuthenticateDigestOrKey 'opaque' => 'required|in:'.$this->getOpaque(), //'uri' => 'in:/'.$request->path(), 'qop' => 'required|in:auth', - 'realm' => 'required|in:'.$resolvedRealm, + 'realm' => 'required|in:'.$account->resolvedRealm, 'nc' => 'required', 'cnonce' => 'required', 'algorithm' => [ @@ -128,7 +126,7 @@ class AuthenticateDigestOrKey // Hashing and checking $a1 = $password->algorithm == 'CLRTXT' - ? hash($hash, $account->username.':'.$resolvedRealm.':'.$password->password) + ? hash($hash, $account->username.':'.$account->resolvedRealm.':'.$password->password) : $password->password; // username:realm/domain:password $a2 = hash($hash, $request->method().':'.$auth['uri']); @@ -199,21 +197,20 @@ class AuthenticateDigestOrKey private function generateAuthHeaders(Account $account, string $nonce): array { $headers = []; - $resolvedRealm = space()?->account_realm ?? $account->domain; foreach ($account->passwords as $password) { if ($password->algorithm == 'CLRTXT') { foreach (array_keys(passwordAlgorithms()) as $algorithm) { array_push( $headers, - $this->generateAuthHeader($resolvedRealm, $algorithm, $nonce) + $this->generateAuthHeader($account->resolvedRealm, $algorithm, $nonce) ); } break; } elseif (\in_array($password->algorithm, array_keys(passwordAlgorithms()))) { array_push( $headers, - $this->generateAuthHeader($resolvedRealm, $password->algorithm, $nonce) + $this->generateAuthHeader($account->resolvedRealm, $password->algorithm, $nonce) ); } } diff --git a/flexiapi/database/factories/PasswordFactory.php b/flexiapi/database/factories/PasswordFactory.php index 0a5f7f4..cc5b2f1 100644 --- a/flexiapi/database/factories/PasswordFactory.php +++ b/flexiapi/database/factories/PasswordFactory.php @@ -30,11 +30,10 @@ class PasswordFactory extends Factory public function definition() { $account = Account::factory()->create(); - $realm = space()?->account_realm ?? $account->domain; return [ 'account_id' => $account->id, - 'password' => hash('md5', $account->username.':'.$realm.':testtest'), + 'password' => hash('md5', $account->username.':'.$account->resolvedRealm.':testtest'), 'algorithm' => 'MD5', ]; } @@ -54,10 +53,9 @@ class PasswordFactory extends Factory { return $this->state(function (array $attributes) { $account = Account::find($attributes['account_id']); - $realm = space()?->account_realm ?? $account->domain; return [ - 'password' => hash('sha256', $account->username.':'.$realm.':testtest'), + 'password' => hash('sha256', $account->username.':'.$account->resolvedRealm.':testtest'), 'account_id' => $account->id, 'algorithm' => 'SHA-256', ]; diff --git a/flexiapi/lang/fr.json b/flexiapi/lang/fr.json index aea9481..33554f5 100644 --- a/flexiapi/lang/fr.json +++ b/flexiapi/lang/fr.json @@ -40,7 +40,7 @@ "Change your email": "Changer votre email", "Change your phone number": "Changer votre numéro de téléphone", "Code Verification" : "Vérification du code", - "instant messaging": "messagerie instantanée", + "Instant Messaging": "Messagerie Instantanée", "Check the README.md documentation": "Voir la documentation dans README.md", "Clear to never expire": "Laisser vide pour ne jamais expirer", "Code": "Code", diff --git a/flexiapi/resources/views/admin/space/configuration.blade.php b/flexiapi/resources/views/admin/space/configuration.blade.php index 0a15f61..85e989f 100644 --- a/flexiapi/resources/views/admin/space/configuration.blade.php +++ b/flexiapi/resources/views/admin/space/configuration.blade.php @@ -68,7 +68,7 @@
- + accounts()->count() > 0)disabled @endif id="account_realm" placeholder="server.tld" value="{{ $space->account_realm }}"> A custom realm for the Space accounts @include('parts.errors', ['name' => 'account_realm']) diff --git a/flexiapi/resources/views/admin/space/create.blade.php b/flexiapi/resources/views/admin/space/create.blade.php index ff2125e..9dc4d98 100644 --- a/flexiapi/resources/views/admin/space/create.blade.php +++ b/flexiapi/resources/views/admin/space/create.blade.php @@ -44,6 +44,14 @@ @include('parts.form.toggle', ['object' => $space, 'key' => 'super', 'label' => __('Super space'), 'supporting' => __('All the admins will be super admins')]) +
+ + + @include('parts.errors', ['name' => 'account_realm']) + {{ __('Leave empty if similar to the domain') }} +
+ +
diff --git a/flexiapi/resources/views/admin/space/edit.blade.php b/flexiapi/resources/views/admin/space/edit.blade.php index 602356b..29aff4d 100644 --- a/flexiapi/resources/views/admin/space/edit.blade.php +++ b/flexiapi/resources/views/admin/space/edit.blade.php @@ -27,7 +27,7 @@

{{ __('Features') }}

- @include('parts.form.toggle', ['object' => $space, 'key' => 'disable_chat_feature', 'label' => __('instant messaging'), 'reversed' => true]) + @include('parts.form.toggle', ['object' => $space, 'key' => 'disable_chat_feature', 'label' => __('Instant Messaging'), 'reversed' => true]) @include('parts.form.toggle', ['object' => $space, 'key' => 'disable_meetings_feature', 'label' => __('Meeting'), 'reversed' => true]) @include('parts.form.toggle', ['object' => $space, 'key' => 'disable_broadcast_feature', 'label' => __('Broadcast'), 'reversed' => true]) @include('parts.form.toggle', ['object' => $space, 'key' => 'hide_settings', 'label' => __('App settings'), 'reversed' => true]) diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index 0e777bf..f7d5403 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -501,6 +501,16 @@ Unblock an account. 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` diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php index 3981c4f..ec4b468 100644 --- a/flexiapi/routes/api.php +++ b/flexiapi/routes/api.php @@ -118,6 +118,8 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo Route::post('{account_id}/block', 'block'); Route::post('{account_id}/unblock', 'unblock'); Route::get('{account_id}/provision', 'provision'); + Route::post('{account_id}/send_provisioning_email', 'sendProvisioningEmail'); + Route::post('{account_id}/send_reset_password_email', 'sendResetPasswordEmail'); Route::post('/', 'store'); Route::put('{account_id}', 'update'); diff --git a/flexiapi/tests/Feature/ApiAccountTest.php b/flexiapi/tests/Feature/ApiAccountTest.php index cbb5e94..bd8a3d5 100644 --- a/flexiapi/tests/Feature/ApiAccountTest.php +++ b/flexiapi/tests/Feature/ApiAccountTest.php @@ -669,6 +669,49 @@ class ApiAccountTest extends TestCase ->json('GET', '/api/accounts/me'); } + public function testSendProvisioningEmail() + { + $password = Password::factory()->create(); + $account = $password->account; + + $admin = Account::factory()->admin()->create(); + $admin->generateUserApiKey(); + $admin->save(); + + $this->keyAuthenticated($admin) + ->json('POST', $this->route . '/' . $account->id . '/send_provisioning_email') + ->assertStatus(403); + + $account->email = 'test@email.com'; + $account->save(); + + $this->keyAuthenticated($admin) + ->json('POST', $this->route . '/' . $account->id . '/send_provisioning_email') + ->assertStatus(200); + } + + public function testSendResetPasswordEmail() + { + $password = Password::factory()->create(); + $account = $password->account; + + $admin = Account::factory()->admin()->create(); + $admin->generateUserApiKey(); + $admin->save(); + + $this->keyAuthenticated($admin) + ->json('POST', $this->route . '/' . $account->id . '/send_reset_password_email') + ->assertStatus(403); + + $account->email = 'test@email.com'; + $account->save(); + + $this->keyAuthenticated($admin) + ->json('POST', $this->route . '/' . $account->id . '/send_reset_password_email') + ->assertStatus(200); + } + + public function testEditAdmin() { $password = Password::factory()->create();