diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ff6883..054408e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
------
diff --git a/flexiapi/.env.example b/flexiapi/.env.example
index 5e6edd5..0876614 100644
--- a/flexiapi/.env.example
+++ b/flexiapi/.env.example
@@ -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
diff --git a/flexiapi/app/Http/Controllers/Account/AccountController.php b/flexiapi/app/Http/Controllers/Account/AccountController.php
index 4eedec1..1ee14f6 100644
--- a/flexiapi/app/Http/Controllers/Account/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Account/AccountController.php
@@ -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();
diff --git a/flexiapi/app/Http/Controllers/Account/EmailController.php b/flexiapi/app/Http/Controllers/Account/EmailController.php
index 904de2d..44e962c 100644
--- a/flexiapi/app/Http/Controllers/Account/EmailController.php
+++ b/flexiapi/app/Http/Controllers/Account/EmailController.php
@@ -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');
diff --git a/flexiapi/app/Http/Controllers/Account/PhoneController.php b/flexiapi/app/Http/Controllers/Account/PhoneController.php
index 3b6b12c..669baef 100644
--- a/flexiapi/app/Http/Controllers/Account/PhoneController.php
+++ b/flexiapi/app/Http/Controllers/Account/PhoneController.php
@@ -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');
diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php
index 39f2371..f7e3a15 100644
--- a/flexiapi/app/Http/Controllers/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php
@@ -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');
diff --git a/flexiapi/app/Http/Controllers/Api/Account/EmailController.php b/flexiapi/app/Http/Controllers/Api/Account/EmailController.php
index bb78298..d61a92c 100644
--- a/flexiapi/app/Http/Controllers/Api/Account/EmailController.php
+++ b/flexiapi/app/Http/Controllers/Api/Account/EmailController.php
@@ -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);
}
}
diff --git a/flexiapi/app/Http/Controllers/Api/Account/PhoneController.php b/flexiapi/app/Http/Controllers/Api/Account/PhoneController.php
index beb81fc..e0bd025 100644
--- a/flexiapi/app/Http/Controllers/Api/Account/PhoneController.php
+++ b/flexiapi/app/Http/Controllers/Api/Account/PhoneController.php
@@ -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);
}
diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
index e3635a6..2f269e3 100644
--- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
@@ -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);
diff --git a/flexiapi/app/Http/Kernel.php b/flexiapi/app/Http/Kernel.php
index d7a3602..9530eed 100644
--- a/flexiapi/app/Http/Kernel.php
+++ b/flexiapi/app/Http/Kernel.php
@@ -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,
diff --git a/flexiapi/app/Http/Middleware/CheckBlocked.php b/flexiapi/app/Http/Middleware/CheckBlocked.php
new file mode 100644
index 0000000..098b1ff
--- /dev/null
+++ b/flexiapi/app/Http/Middleware/CheckBlocked.php
@@ -0,0 +1,35 @@
+.
+*/
+
+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);
+ }
+}
diff --git a/flexiapi/app/Services/BlockingService.php b/flexiapi/app/Services/BlockingService.php
new file mode 100644
index 0000000..b391a54
--- /dev/null
+++ b/flexiapi/app/Services/BlockingService.php
@@ -0,0 +1,86 @@
+.
+*/
+
+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;
+ }
+}
diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php
index 2e6149f..cfbf626 100644
--- a/flexiapi/config/app.php
+++ b/flexiapi/config/app.php
@@ -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
*/
diff --git a/flexiapi/database/migrations/2024_01_22_151512_add_blocked_to_accounts_table.php b/flexiapi/database/migrations/2024_01_22_151512_add_blocked_to_accounts_table.php
new file mode 100644
index 0000000..29bb216
--- /dev/null
+++ b/flexiapi/database/migrations/2024_01_22_151512_add_blocked_to_accounts_table.php
@@ -0,0 +1,22 @@
+boolean('blocked')->default(false)->index();
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('accounts', function (Blueprint $table) {
+ $table->dropColumn('blocked');
+ });
+ }
+};
diff --git a/flexiapi/resources/views/account/blocked.blade.php b/flexiapi/resources/views/account/blocked.blade.php
new file mode 100644
index 0000000..99040d8
--- /dev/null
+++ b/flexiapi/resources/views/account/blocked.blade.php
@@ -0,0 +1,7 @@
+@extends('layouts.main', ['welcome' => true])
+
+@section('content')
+
+
Your account was blocked due to its recent activity, please contact support to unblock it
+
+@endsection
\ No newline at end of file
diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php
index 0428825..dbe3ed0 100644
--- a/flexiapi/resources/views/admin/account/create_edit.blade.php
+++ b/flexiapi/resources/views/admin/account/create_edit.blade.php
@@ -39,7 +39,7 @@