Fix FLEXIAPI-134 Add a system to detect and block abusive accounts

This commit is contained in:
Timothée Jaussoin 2024-01-22 17:09:57 +01:00
parent e4ccfe7a4a
commit 4035cbd0ab
34 changed files with 361 additions and 77 deletions

View file

@ -13,6 +13,8 @@ v1.5
- Fix #133 Make the MySQL connection unstrict
- Fix #132 Move the provisioning_tokens and recovery_codes to dedicated table
- Fix #130 Drop the group column in the Accounts table
- Fix FLEXIAPI-132 Refactor the Provisioning to remove proxy_default_values
- Fix FLEXIAPI-134 Add a system to detect and block abusive accounts
v1.4.4
------

View file

@ -33,6 +33,10 @@ ACCOUNT_PROVISIONING_RC_FILE=
ACCOUNT_PROVISIONING_OVERWRITE_ALL=
ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER=true
# Blocking service
BLOCKING_TIME_PERIOD_CHECK=30 # Time span on which the blocking service will proceed, in minutes
BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5 # Amount of account events authorized during this period
# Instance specific parameters
INSTANCE_COPYRIGHT= # Simple text displayed in the page footer
INSTANCE_INTRO_REGISTRATION= # Markdown text displayed in the home page

View file

@ -36,6 +36,11 @@ class AccountController extends Controller
]);
}
public function blocked(Request $request)
{
return view('account.blocked');
}
public function panel(Request $request)
{
return view('account.dashboard', [
@ -47,7 +52,7 @@ class AccountController extends Controller
{
$account = (new AccountService(api: false))->store($request);
$request->validate(['g-recaptcha-response' => captchaConfigured() ? 'required|captcha': '']);
$request->validate(['g-recaptcha-response' => captchaConfigured() ? 'required|captcha' : '']);
Auth::login($account);
@ -74,7 +79,7 @@ class AccountController extends Controller
$request->validate(['identifier' => 'required|same:identifier_confirm']);
if (!$request->user()->hasTombstone()) {
$tombstone = new AccountTombstone;
$tombstone = new AccountTombstone();
$tombstone->username = $request->user()->username;
$tombstone->domain = $request->user()->domain;
$tombstone->save();

View file

@ -36,6 +36,10 @@ class EmailController extends Controller
{
$request->validate(['g-recaptcha-response' => captchaConfigured() ? 'required|captcha': '']);
if ((new BlockingService($request->user()))->checkBlock()) {
return redirect()->route('account.blocked');
}
(new AccountService(api: false))->requestEmailChange($request);
return redirect()->route('account.email.validate');

View file

@ -36,6 +36,10 @@ class PhoneController extends Controller
{
$request->validate(['g-recaptcha-response' => captchaConfigured() ? 'required|captcha': '']);
if ((new BlockingService($request->user()))->checkBlock()) {
return redirect()->route('account.blocked');
}
(new AccountService(api: false))->requestPhoneChange($request);
return redirect()->route('account.phone.validate');

View file

@ -95,6 +95,7 @@ class AccountController extends Controller
$account->user_agent = config('app.name');
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->activated = $request->get('activated') == 'true';
$account->blocked = $request->get('blocked') == 'true';
$account->save();
$account->phone = $request->get('phone');
@ -132,6 +133,7 @@ class AccountController extends Controller
$account->display_name = $request->get('display_name');
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->activated = $request->get('activated') == 'true';
$account->blocked = $request->get('blocked') == 'true';
$account->save();
$account->phone = $request->get('phone');

View file

@ -21,12 +21,18 @@ namespace App\Http\Controllers\Api\Account;
use App\Http\Controllers\Controller;
use App\Services\AccountService;
use App\Services\BlockingService;
use Illuminate\Http\Request;
class EmailController extends Controller
{
public function requestUpdate(Request $request)
{
if ((new BlockingService($request->user()))->checkBlock()) {
return abort(403, 'Account blocked');
}
(new AccountService)->requestEmailChange($request);
}
}

View file

@ -19,15 +19,20 @@
namespace App\Http\Controllers\Api\Account;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\AccountService;
use App\Services\BlockingService;
use Illuminate\Http\Request;
class PhoneController extends Controller
{
public function requestUpdate(Request $request)
{
if ((new BlockingService($request->user()))->checkBlock()) {
return abort(403, 'Account blocked');
}
return (new AccountService)->requestPhoneChange($request);
}

View file

@ -96,6 +96,28 @@ class AccountController extends Controller
return $account;
}
public function block(int $id)
{
$account = Account::findOrFail($id);
$account->blocked = true;
$account->save();
Log::channel('events')->info('API Admin: Account blocked', ['id' => $account->identifier]);
return $account;
}
public function unblock(int $id)
{
$account = Account::findOrFail($id);
$account->blocked = false;
$account->save();
Log::channel('events')->info('API Admin: Account unblocked', ['id' => $account->identifier]);
return $account;
}
public function provision(int $id)
{
$account = Account::findOrFail($id);

View file

@ -72,6 +72,7 @@ class Kernel extends HttpKernel
'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.digest_or_key' => \App\Http\Middleware\AuthenticateDigestOrKey::class,
'auth.check_blocked' => \App\Http\Middleware\CheckBlocked::class,
'web_panel_enabled' => \App\Http\Middleware\IsWebPanelEnabled::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

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

View file

@ -0,0 +1,86 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Services;
use App\Account;
use Carbon\Carbon;
class BlockingService
{
public function __construct(public Account $account)
{
}
public function checkBlock(): bool
{
if ($this->account->blocked) {
return true;
}
$isBlockable = $this->isBlockable();
if ($isBlockable) {
$this->account->blocked = true;
$this->account->save();
}
return $isBlockable;
}
public function isBlockable(): bool
{
if (config('app.blocking_amount_events_authorized_during_period') == 0) {
return false;
}
return $this->countEvents() >= config('app.blocking_amount_events_authorized_during_period');
}
private function countEvents(): int
{
$events = 0;
$events += $this->account->recoveryCodes()->where(
'created_at',
'>',
Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->toDateTimeString()
)->count();
$events += $this->account->phoneChangeCodes()->where(
'created_at',
'>',
Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->toDateTimeString()
)->count();
$events += $this->account->emailChangeCodes()->where(
'created_at',
'>',
Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->toDateTimeString()
)->count();
// Deprecated, also detect if the account itself was updated recently, might be because of the confirmation_key change
if (Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->isBefore($this->account->updated_at)) {
$events++;
}
return $events;
}
}

View file

@ -51,6 +51,12 @@ return [
'flexisip_pusher_firebase_key' => env('APP_FLEXISIP_PUSHER_FIREBASE_KEY', null),
'linphone_daemon_unix_pipe' => env('APP_LINPHONE_DAEMON_UNIX_PATH', null),
/**
* Blocking service
*/
'blocking_time_period_check' => env('BLOCKING_TIME_PERIOD_CHECK', 30),
'blocking_amount_events_authorized_during_period' => env('BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD', 5),
/**
* Account provisioning
*/

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('accounts', function (Blueprint $table) {
$table->boolean('blocked')->default(false)->index();
});
}
public function down()
{
Schema::table('accounts', function (Blueprint $table) {
$table->dropColumn('blocked');
});
}
};

View file

@ -0,0 +1,7 @@
@extends('layouts.main', ['welcome' => true])
@section('content')
<div>
<p>Your account was blocked due to its recent activity, please contact support to unblock it</p>
</div>
@endsection

View file

@ -39,7 +39,7 @@
<h2>Connexion</h2>
<div>
<input placeholder="Username" required="required" name="username" type="text"
value="@if ($account->id) {{ $account->username }}@else{{ old('username') }} @endif"
value="@if ($account->id){{ $account->username }}@else{{ old('username') }} @endif"
@if ($account->id) readonly @endif>
<label for="username">Username</label>
@include('parts.errors', ['name' => 'username'])
@ -53,7 +53,7 @@
<div>
<input placeholder="John Doe" name="display_name" type="text"
value="@if ($account->id) {{ $account->display_name }}@else{{ old('display_name') }} @endif">
value="@if ($account->id){{ $account->display_name }}@else{{ old('display_name') }} @endif">
<label for="display_name">Display Name</label>
@include('parts.errors', ['name' => 'display_name'])
</div>
@ -97,6 +97,14 @@
<label>Status</label>
</div>
<div>
<input name="blocked" value="false" type="radio" @if (!$account->blocked) checked @endif>
<p>Unblocked</p>
<input name="blocked" value="true" type="radio" @if ($account->blocked) checked @endif>
<p>Blocked</p>
<label>Blocked account</label>
</div>
<div>
<input name="role" value="admin" type="radio" @if ($account->admin) checked @endif>
<p>Admin</p>

View file

@ -104,9 +104,6 @@
@endif
</td>
<td>
@if ($account->email)
<span class="badge badge-info">Email</span>
@endif
@if ($account->activated)
<span class="badge badge-success" title="Activated">Act.</span>
@endif
@ -116,6 +113,9 @@
@if ($account->sha256Password)
<span class="badge badge-info">SHA256</span>
@endif
@if ($account->blocked)
<span class="badge badge-error">Blocked</span>
@endif
</td>
<td>{{ $account->updated_at }}</td>
</tr>

View file

@ -386,18 +386,30 @@ Search for a specific account by email.
Delete a specific account and its related information.
### `GET /accounts/{id}/activate`
### `POST /accounts/{id}/activate`
<span class="badge badge-warning">Admin</span>
Activate an account.
### `GET /accounts/{id}/deactivate`
### `POST /accounts/{id}/deactivate`
<span class="badge badge-warning">Admin</span>
Deactivate an account.
### `POST /accounts/{id}/block`
<span class="badge badge-warning">Admin</span>
Block an account.
### `POST /accounts/{id}/unblock`
<span class="badge badge-warning">Admin</span>
Unblock an account.
### `GET /accounts/{id}/provision`
<span class="badge badge-warning">Admin</span>

View file

@ -56,7 +56,7 @@ Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store');
Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
Route::group(['middleware' => ['auth.digest_or_key']], function () {
Route::group(['middleware' => ['auth.digest_or_key', 'auth.check_blocked']], function () {
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
Route::prefix('accounts/me')->group(function () {
@ -90,8 +90,10 @@ Route::group(['middleware' => ['auth.digest_or_key']], function () {
// Accounts
Route::prefix('accounts')->controller(AdminAccountController::class)->group(function () {
Route::get('{id}/activate', 'activate');
Route::get('{id}/deactivate', 'deactivate');
Route::post('{id}/activate', 'activate');
Route::post('{id}/deactivate', 'deactivate');
Route::post('{id}/block', 'block');
Route::post('{id}/unblock', 'unblock');
Route::get('{id}/provision', 'provision');
Route::post('/', 'store');

View file

@ -91,6 +91,12 @@ Route::middleware(['web_panel_enabled'])->group(function () {
});
Route::middleware(['auth'])->group(function () {
Route::get('logout', 'Account\AuthenticateController@logout')->name('account.logout');
});
Route::middleware(['auth', 'auth.check_blocked'])->group(function () {
Route::get('blocked', 'Account\AccountController@blocked')->name('account.blocked');
// Email change and validation
Route::prefix('email')->controller(EmailController::class)->group(function () {
Route::get('change', 'change')->name('account.email.change');
@ -114,8 +120,6 @@ Route::middleware(['web_panel_enabled'])->group(function () {
Route::delete('delete', 'destroy')->name('account.destroy');
});
Route::get('logout', 'Account\AuthenticateController@logout')->name('account.logout');
Route::prefix('password')->controller(PasswordController::class)->group(function () {
Route::get('/', 'show')->name('account.password.show');
Route::post('/', 'update')->name('account.password.update');
@ -134,7 +138,7 @@ Route::middleware(['web_panel_enabled'])->group(function () {
Route::get('auth_tokens/qrcode/{token}', 'Account\AuthTokenController@qrcode')->name('auth_tokens.qrcode');
Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth');
Route::name('admin.')->prefix('admin')->middleware(['auth.admin'])->group(function () {
Route::name('admin.')->prefix('admin')->middleware(['auth.admin', 'auth.check_blocked'])->group(function () {
Route::name('statistics.')->controller(StatisticsController::class)->prefix('statistics')->group(function () {
Route::get('/', 'index')->name('index');
Route::post('call_logs', 'editCallLogs')->name('edit_call_logs');

View file

@ -0,0 +1,75 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2021 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Tests\Feature;
use App\Admin;
use App\Password;
use Tests\TestCase;
class AccountBlockingTest extends TestCase
{
protected $route = '/api/accounts';
protected $method = 'POST';
public function testBlocking()
{
$password = Password::factory()->create();
$password->account->generateApiKey();
config()->set('app.blocking_amount_events_authorized_during_period', 2);
$this->keyAuthenticated($password->account)
->json($this->method, $this->route . '/me/phone/request', [
'phone' => '+331234'
])->assertStatus(200);
$this->keyAuthenticated($password->account)
->json($this->method, $this->route . '/me/email/request', [
'email' => 'foo@bar.com'
])->assertStatus(403);
}
public function testAdminBlocking()
{
$password = Password::factory()->create();
$password->account->generateApiKey();
$admin = Admin::factory()->create();
$admin->account->generateApiKey();
$this->keyAuthenticated($password->account)
->get($this->route . '/me')->assertStatus(200);
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route . '/' . $password->account->id .'/block')
->assertStatus(200);
$this->keyAuthenticated($password->account)
->get($this->route . '/me')->assertStatus(403);
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route . '/' . $password->account->id .'/unblock')
->assertStatus(200);
$this->keyAuthenticated($password->account)
->get($this->route . '/me')->assertStatus(200);
}
}

View file

@ -19,7 +19,6 @@
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Password;
@ -29,8 +28,6 @@ use App\AuthToken;
class AccountProvisioningTest extends TestCase
{
use RefreshDatabase;
protected $route = '/provisioning';
protected $accountRoute = '/provisioning/me';
protected $method = 'GET';

View file

@ -23,13 +23,10 @@ use App\Password;
use App\AccountAction;
use App\Admin;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ApiAccountActionTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts';
protected $method = 'POST';

View file

@ -19,15 +19,11 @@
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Password;
class ApiAccountApiKeyTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts/me/api_key';
protected $method = 'GET';

View file

@ -23,14 +23,11 @@ use App\Password;
use App\AccountType;
use App\Admin;
use App\ContactsList;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class ApiAccountContactTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts';
protected $contactsListsRoute = '/api/contacts_lists';
protected $method = 'POST';

View file

@ -21,17 +21,13 @@ namespace Tests\Feature;
use App\Account;
use App\AccountCreationRequestToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\AccountCreationToken;
use App\Admin;
use Tests\TestCase;
use Carbon\Carbon;
class ApiAccountCreationTokenTest extends TestCase
{
use RefreshDatabase;
protected $tokenRoute = '/api/account_creation_tokens/send-by-push';
protected $tokenRequestRoute = '/api/account_creation_request_tokens';
protected $tokenUsingCreationTokenRoute = '/api/account_creation_tokens/using-account-creation-request-token';

View file

@ -23,14 +23,11 @@ use App\Password;
use App\AccountType;
use App\Admin;
use App\ContactsList;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class ApiAccountDictionaryTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts';
protected $method = 'POST';
@ -49,7 +46,7 @@ class ApiAccountDictionaryTest extends TestCase
// First key
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $key , [
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $key, [
'value' => $value
])->assertStatus(201);
@ -71,7 +68,7 @@ class ApiAccountDictionaryTest extends TestCase
// Update
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $key , [
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $key, [
'value' => $newValue
])->assertStatus(200);
@ -84,7 +81,7 @@ class ApiAccountDictionaryTest extends TestCase
// Second key
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $secondKey , [
->json($this->method, $this->route . '/' . $account->id . ' /dictionary/' . $secondKey, [
'value' => $newValue
])->assertStatus(201);

View file

@ -21,14 +21,11 @@ namespace Tests\Feature;
use App\Admin;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
class ApiAccountMessageTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/messages';
protected $method = 'POST';

View file

@ -21,14 +21,10 @@ namespace Tests\Feature;
use App\Password;
use App\PhoneChangeCode;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ApiAccountPhoneChangeTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts/me/phone';
protected $method = 'POST';

View file

@ -27,14 +27,10 @@ use App\ActivationExpiration;
use App\Admin;
use App\Alias as AppAlias;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ApiAccountTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts';
protected $method = 'POST';
@ -506,7 +502,7 @@ class ApiAccountTest extends TestCase
$password->account->activated = false;
$password->account->save();
$expiration = new ActivationExpiration;
$expiration = new ActivationExpiration();
$expiration->account_id = $password->account->id;
$expiration->expires = Carbon::now()->subYear();
$expiration->save();
@ -698,7 +694,7 @@ class ApiAccountTest extends TestCase
$phone = '+1234';
$alias = new AppAlias;
$alias = new AppAlias();
$alias->alias = $phone;
$alias->domain = $password->account->domain;
$alias->account_id = $password->account->id;
@ -743,7 +739,7 @@ class ApiAccountTest extends TestCase
config()->set('app.dangerous_endpoints', true);
$alias = new AppAlias;
$alias = new AppAlias();
$alias->alias = $phone;
$alias->domain = $password->account->domain;
$alias->account_id = $password->account->id;
@ -949,7 +945,7 @@ class ApiAccountTest extends TestCase
$password->account->activated = false;
$password->account->save();
$expiration = new ActivationExpiration;
$expiration = new ActivationExpiration();
$expiration->account_id = $password->account->id;
$expiration->expires = Carbon::now()->subYear();
$expiration->save();
@ -984,6 +980,8 @@ class ApiAccountTest extends TestCase
public function testChangeEmail()
{
$this->disableBlockingService();
$password = Password::factory()->create();
$otherAccount = Password::factory()->create();
$password->account->generateApiKey();
@ -1109,7 +1107,7 @@ class ApiAccountTest extends TestCase
// deactivate
$this->keyAuthenticated($admin->account)
->get($this->route . '/' . $password->account->id . '/deactivate')
->post($this->route . '/' . $password->account->id . '/deactivate')
->assertStatus(200)
->assertJson([
'activated' => false
@ -1123,7 +1121,7 @@ class ApiAccountTest extends TestCase
]);
$this->keyAuthenticated($admin->account)
->get($this->route . '/' . $password->account->id . '/activate')
->post($this->route . '/' . $password->account->id . '/activate')
->assertStatus(200)
->assertJson([
'activated' => true
@ -1225,7 +1223,8 @@ class ApiAccountTest extends TestCase
->assertStatus(200)
->assertJson([
'confirmation_key_expires' => '2040-12-12 12:12:12'
]);;
]);
;
}
public function testDelete()

View file

@ -23,14 +23,11 @@ use App\Password;
use App\AccountType;
use App\Admin;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class ApiAccountTypeTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/account_types';
protected $method = 'POST';

View file

@ -21,13 +21,10 @@ namespace Tests\Feature;
use App\Password;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ApiAuthenticationTest extends TestCase
{
use RefreshDatabase;
protected $route = '/api/accounts/me';
protected $method = 'GET';

View file

@ -23,13 +23,12 @@ use App\Account;
use App\Admin;
use App\StatisticsCallDevice;
use App\StatisticsMessageDevice;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class ApiStatisticsTest extends TestCase
{
use WithFaker, RefreshDatabase;
use WithFaker;
protected $routeMessages = '/api/statistics/messages';
protected $routeCalls = '/api/statistics/calls';

View file

@ -23,14 +23,21 @@ use App\Password;
use App\Account;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use RefreshDatabase;
protected $route = '/api/accounts/me';
protected $method = 'GET';
protected function disableBlockingService()
{
config()->set('app.blocking_amount_events_authorized_during_period', 0);
}
protected function keyAuthenticated(Account $account)
{
return $this->withHeaders([
@ -41,14 +48,14 @@ abstract class TestCase extends BaseTestCase
protected function generateFirstResponse(Password $password, ?string $method = null, ?string $route = null)
{
return $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
'From' => 'sip:' . $password->account->identifier
])->json($method ?? $this->method, $route ?? $this->route);
}
protected function generateSecondResponse(Password $password, $firstResponse)
{
return $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'From' => 'sip:' . $password->account->identifier,
'Authorization' => $this->generateDigest($password, $firstResponse),
]);
}