mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 01:58:07 +00:00
Fix/325 328
This commit is contained in:
parent
b590995b4e
commit
724e4c4e5b
12 changed files with 124 additions and 15 deletions
|
|
@ -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
|
||||
----
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<input name="account_realm" id="account_realm" placeholder="server.tld" value="{{ $space->account_realm }}">
|
||||
<input name="account_realm" @if ($space->accounts()->count() > 0)disabled @endif id="account_realm" placeholder="server.tld" value="{{ $space->account_realm }}">
|
||||
<label for="account_realm">Account realm</label>
|
||||
<span class="supporting">A custom realm for the Space accounts</span>
|
||||
@include('parts.errors', ['name' => 'account_realm'])
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@
|
|||
|
||||
@include('parts.form.toggle', ['object' => $space, 'key' => 'super', 'label' => __('Super space'), 'supporting' => __('All the admins will be super admins')])
|
||||
|
||||
<div>
|
||||
<input placeholder="realp.sip" name="account_realm" type="text" pattern="{{ $space::DOMAIN_REGEX}}" value="{{ $space->account_realm ?? old('account_realm') }}">
|
||||
<label for="username">{{ __('Account realm') }}</label>
|
||||
@include('parts.errors', ['name' => 'account_realm'])
|
||||
<span class="supporting">{{ __('Leave empty if similar to the domain') }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="large">
|
||||
<input class="btn" type="submit" value="{{ __('Create') }}">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<h3 class="large">{{ __('Features') }}</h3>
|
||||
|
||||
@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])
|
||||
|
|
|
|||
|
|
@ -501,6 +501,16 @@ Unblock an account.
|
|||
|
||||
Provision an account by generating a fresh `provisioning_token`.
|
||||
|
||||
### `POST /accounts/{id}/send_provisioning_email`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Send a provisioning email to the account.
|
||||
|
||||
### `POST /accounts/{id}/send_reset_password_email`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Send a password reset email to the account.
|
||||
|
||||
## Accounts email
|
||||
|
||||
### `POST /accounts/me/email/request`
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue