Fix FLEXIAPI-241 Add a /push-notification endpoint to send custom push...

This commit is contained in:
Timothée Jaussoin 2024-12-10 16:05:19 +00:00
parent 694265cc1c
commit 9cb24cad77
9 changed files with 168 additions and 5 deletions

View file

@ -6,6 +6,7 @@ v1.7
- Fix FLEXIAPI-220 Migrate SIP Domains to Spaces - Fix FLEXIAPI-220 Migrate SIP Domains to Spaces
- Fix GH-15 Add password import from CSV - Fix GH-15 Add password import from CSV
- Fix FLEXIAPI-242 Add stricter validation for the AccountCreationToken Push Notification endpoint - Fix FLEXIAPI-242 Add stricter validation for the AccountCreationToken Push Notification endpoint
- Fix FLEXIAPI-241 Add a /push-notification endpoint to send custom push notifications to the Flexisip Pusher
v1.6 v1.6
---- ----

View file

@ -64,7 +64,6 @@ class CreationTokenController extends Controller
$token->pn_prid = $request->get('pn_prid'); $token->pn_prid = $request->get('pn_prid');
$token->fillRequestInfo($request); $token->fillRequestInfo($request);
// Send the token to the device via Push Notification
$fp = new FlexisipPusherConnector($token->pn_provider, $token->pn_param, $token->pn_prid); $fp = new FlexisipPusherConnector($token->pn_provider, $token->pn_param, $token->pn_prid);
if ($fp->sendToken($token->token)) { if ($fp->sendToken($token->token)) {
Log::channel('events')->info('API: Token sent', ['token' => $token->token]); Log::channel('events')->info('API: Token sent', ['token' => $token->token]);

View file

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\Account;
use App\Http\Controllers\Controller;
use App\Libraries\FlexisipPusherConnector;
use App\Rules\PnParam;
use App\Rules\PnPrid;
use App\Rules\PnProvider;
use App\Rules\CallId;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class PushNotificationController extends Controller
{
public function push(Request $request)
{
$request->validate([
'pn_provider' => ['required', new PnProvider],
'pn_param' => [new PnParam],
'pn_prid' => [new PnPrid],
'type' => ['required', Rule::in(array_keys(FlexisipPusherConnector::$apnsTypes))],
'call_id' => [new CallId],
]);
$fp = new FlexisipPusherConnector($request->get('pn_provider'), $request->get('pn_param'), $request->get('pn_prid'));
if ($fp->send(callId: $request->get('call_id'), type: $request->get('type'))) {
Log::channel('events')->info('API: Push notification sent', [
'call_id' => $request->get('call_id'),
'type' => $request->get('type')
]);
return;
}
abort(503, "Push notification not sent");
}
}

View file

@ -28,6 +28,11 @@ class FlexisipPusherConnector
private ?string $pnParam = null; private ?string $pnParam = null;
private ?string $pnPrid = null; private ?string $pnPrid = null;
private ?string $pusherFirebaseKey = null; private ?string $pusherFirebaseKey = null;
public static array $apnsTypes = [
'background' => 'Background',
'message' => 'RemoteWithMutableContent',
'call' => 'PushKit'
];
public function __construct(string $pnProvider, string $pnParam, string $pnPrid) public function __construct(string $pnProvider, string $pnParam, string $pnPrid)
{ {
@ -63,15 +68,27 @@ class FlexisipPusherConnector
{ {
$payload = json_encode(['token' => $token]); $payload = json_encode(['token' => $token]);
$this->send(payload: $payload);
}
public function send(?string $payload = null, ?string $callId = null, ?string $type = 'background')
{
if (!empty($this->pusherPath)) { if (!empty($this->pusherPath)) {
$command = $this->pusherPath $command = $this->pusherPath
. " --pn-provider '" . $this->pnProvider . "'" . " --pn-provider '" . $this->pnProvider . "'"
. " --pn-param '" . $this->pnParam . "'" . " --pn-param '" . $this->pnParam . "'"
. " --pn-prid '" . $this->pnPrid . "'" . " --pn-prid '" . $this->pnPrid . "'";
. " --customPayload '" . $payload . "'";
if (in_array($this->pnProvider, ['apns', 'apns.dev'])) { if ($payload != null) {
$command .= " --apple-push-type Background"; $command .= " --customPayload '" . $payload . "'";
}
if ($callId != null) {
$command .= " --call-id '" . $callId . "'";
}
if (in_array($this->pnProvider, ['apns', 'apns.dev']) && in_array($type, $this->apnsTypes)) {
$command .= " --apple-push-type " . $this->apnsTypes[$type];
} }
if ($this->pusherFirebaseKey) { if ($this->pusherFirebaseKey) {

View file

@ -0,0 +1,19 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Respect\Validation\Validator;
class CallId implements Rule
{
public function passes($attribute, $value)
{
return Validator::regex('/^[\w\-]+$/')->validate($value);
}
public function message()
{
return 'The :attribute should only contain only alphanumeric and dashes characters';
}
}

View file

@ -805,6 +805,21 @@ JSON parameters:
* `to` **required**, SIP address of the receiver * `to` **required**, SIP address of the receiver
* `body` **required**, content of the message * `body` **required**, content of the message
## Push Notifications
### `POST /push_notification`
<span class="badge badge-warning">User</span>
Send a push notification using the Flexisip Pusher.
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 and colon characters
* `type` **required**, must be in `background`, `message` or `call`
* `call_id` a Call ID, must have only alphanumeric and dashes characters
## Phone Countries ## Phone Countries
The phone numbers managed by FlexiAPI are validated against a list of countries that can be managed in the admin web panels. The phone numbers managed by FlexiAPI are validated against a list of countries that can be managed in the admin web panels.

View file

@ -65,6 +65,8 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach'); Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
Route::post('account_creation_tokens/consume', 'Api\Account\CreationTokenController@consume'); Route::post('account_creation_tokens/consume', 'Api\Account\CreationTokenController@consume');
Route::post('push_notification', 'Api\Account\PushNotificationController@push');
Route::prefix('accounts/me')->group(function () { Route::prefix('accounts/me')->group(function () {
Route::get('api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt'); Route::get('api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt');

View file

@ -62,6 +62,7 @@ class ApiAccountCreationTokenTest extends TestCase
fn ($error) => substr($error, 0, strlen(ValidateJSON::$message)) == ValidateJSON::$message fn ($error) => substr($error, 0, strlen(ValidateJSON::$message)) == ValidateJSON::$message
); );
} }
public function testCorrectParameters() public function testCorrectParameters()
{ {
$this->assertSame(AccountCreationToken::count(), 0); $this->assertSame(AccountCreationToken::count(), 0);

View file

@ -0,0 +1,71 @@
<?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 Tests\Feature;
use App\Account;
use App\Space;
use App\AccountCreationRequestToken;
use App\AccountCreationToken;
use App\Http\Middleware\ValidateJSON;
use Tests\TestCase;
use Carbon\Carbon;
class ApiPushNotificationTest extends TestCase
{
protected $tokenRoute = '/api/push_notification';
protected $method = 'POST';
protected $pnProvider = 'fcm';
protected $pnParam = 'param';
protected $pnPrid = 'id';
protected $type = 'message';
public function testCorrectParameters()
{
$account = Account::factory()->create();
$account->generateApiKey();
$this->keyAuthenticated($account)
->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider,
'pn_param' => $this->pnParam,
'pn_prid' => $this->pnPrid,
'type' => $this->type,
'call_id' => '@blabla@'
])->assertJsonValidationErrors(['call_id']);
$this->keyAuthenticated($account)
->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider,
'pn_param' => $this->pnParam,
'pn_prid' => $this->pnPrid,
'type' => $this->type
])->assertStatus(503);
$this->keyAuthenticated($account)
->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider,
'pn_param' => $this->pnParam,
'pn_prid' => $this->pnPrid,
'type' => $this->type,
'call_id' => 'call_id-123'
])->assertStatus(503);
}
}