Compare commits

...

6 commits

Author SHA1 Message Date
Timothée Jaussoin
b8bc5d5b58 Fix FLEXIAPI-262 Bypass the JWT auth if we have an API 2025-01-30 10:23:58 +00:00
Timothée Jaussoin
14a1df8bcd Fix/254 empty post validate json 16 2025-01-06 08:53:59 +00:00
Timothée Jaussoin
21bced8764 Fix FLEXIAPI-252 Update the hCaptcha Laravel library, use file instead of... 2024-12-18 15:14:45 +00:00
Timothée Jaussoin
d43cb345d2 Fix FLEXIAPI-242 Add stricter validation for the AccountCreationToken Push Notification endpoint 2024-12-10 14:20:47 +01:00
Timothée Jaussoin
1d29bac386 Fix GH-15 Add password import from CSV 2024-12-09 16:07:30 +00:00
Timothée Jaussoin
963a85bd5e Complete the RELEASE.md file for the 1.6 release 2024-11-12 15:52:54 +01:00
24 changed files with 546 additions and 327 deletions

View file

@ -14,13 +14,13 @@ rocky9-deploy:
- rocky9-package - rocky9-package
- rocky9-test - rocky9-test
debian11-deploy: #debian11-deploy:
extends: .deploy # extends: .deploy
script: # script:
- ./deploy_packages.sh debian bullseye # - ./deploy_packages.sh debian bullseye
needs: # needs:
- debian11-package # - debian11-package
- debian11-test # - debian11-test
debian12-deploy: debian12-deploy:
extends: .deploy extends: .deploy

View file

@ -16,9 +16,9 @@ rocky9-package:
script: script:
- make rpm-el9 - make rpm-el9
debian11-package: #debian11-package:
extends: .debian_package # extends: .debian_package
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION # image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
debian12-package: debian12-package:
extends: .debian_package extends: .debian_package

View file

@ -21,11 +21,11 @@ rocky9-test:
- php artisan key:generate - php artisan key:generate
- vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log - vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log
debian11-test: #debian11-test:
extends: .debian-test # extends: .debian-test
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION # image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
needs: # needs:
- debian11-package # - debian11-package
debian12-test: debian12-test:
extends: .debian-test extends: .debian-test

View file

@ -1,8 +1,8 @@
variables: variables:
ROCKY_8_IMAGE_VERSION: 20241112_111033_update_package_and_dependencies ROCKY_8_IMAGE_VERSION: 20241112_111033_update_package_and_dependencies
ROCKY_9_IMAGE_VERSION: 20241112_115442_add_php_sodium ROCKY_9_IMAGE_VERSION: 20241112_115442_add_php_sodium
DEBIAN_11_IMAGE_VERSION: 20241112_113527_update_package_and_dependencies # DEBIAN_11_IMAGE_VERSION: 20241204_161845_update_download_linphone_org
DEBIAN_12_IMAGE_VERSION: 20241112_113948_update_package_and_dependencies DEBIAN_12_IMAGE_VERSION: 20241204_162237_update_download_linphone_org
PHP_REDIS_REMI_VERSION: php-pecl-redis5-5.3.6-1 PHP_REDIS_REMI_VERSION: php-pecl-redis5-5.3.6-1
PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.14-1 PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.14-1
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-1 PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-1

View file

@ -13,6 +13,11 @@ v1.6
- Fix FLEXIAPI-239 Ensure to return the correct error codes as stated in the RFC6750 section 3.1 - Fix FLEXIAPI-239 Ensure to return the correct error codes as stated in the RFC6750 section 3.1
- Fix FLEXIAPI-238 Replace Material Icons with Phosphor - Fix FLEXIAPI-238 Replace Material Icons with Phosphor
- Fix FLEXIAPI-240 Update the Docker images - Fix FLEXIAPI-240 Update the Docker images
- Fix GH-15 Add password import from CSV
- Fix FLEXIAPI-242 Add stricter validation for the AccountCreationToken Push Notification endpoint
- Fix FLEXIAPI-252 Update the hCaptcha Laravel library, use file instead of cookies to store the session to prevent empty errors bags
- Fix FLEXIAPI-254 Allow no data on POST requests to not trigger the ValidateJSON middleware
- Fix FLEXIAPI-262 Bypass the JWT auth if we have an API Key
v1.5 v1.5
--- ---

View file

@ -1,10 +1,28 @@
# Releases # Releases
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/). The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [1.6] - 2024-11-12
## Added
- **Allow the expiration of tokens and codes in the DotEnv configuration**
- **New DotEnv variables:** check all the new `*_EXPIRATION_MINUTES` for each token and code in `.env.example`
- **Phone validation system by country code:** all the provided phone numbers are now properly validated and some countries can be forbidden
- **SIP Domain management:** the account domains are now managed in a set of panels and API endpoints, this is the base of the upcoming space administration system
- **JSON validation in the API:** the provised JSON is now validated and returns an error if an issue is detected
- **CoTURN credentials support:** TURN credentials can now be generated and return through the provisioning feature
## Changed
- **Replace Material Icons with Phosphor**
## Deprecated
- **Last major version supporting the deprecated endpoints of the API**
## [1.5] - 2024-08-29 ## [1.5] - 2024-08-29
### Added ### Added

View file

@ -89,7 +89,7 @@ REDIS_DB=
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_DRIVER=file
QUEUE_CONNECTION=sync QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie SESSION_DRIVER=file
SESSION_LIFETIME=120 SESSION_LIFETIME=120
# SMTP and emails # SMTP and emails

View file

@ -19,6 +19,7 @@
namespace App; namespace App;
use Illuminate\Validation\Rule;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
class AccountCreationToken extends Consommable class AccountCreationToken extends Consommable

View file

@ -20,6 +20,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Account; use App\Account;
use App\Password;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -166,7 +167,7 @@ class AccountImportController extends Controller
$accounts = []; $accounts = [];
$now = \Carbon\Carbon::now(); $now = \Carbon\Carbon::now();
$admins = $phones = []; $admins = $phones = $passwords = [];
foreach ($lines as $line) { foreach ($lines as $line) {
if ($line->role == 'admin') { if ($line->role == 'admin') {
@ -177,6 +178,10 @@ class AccountImportController extends Controller
$phones[$line->username] = $line->phone; $phones[$line->username] = $line->phone;
} }
if (!empty($line->password)) {
$passwords[$line->username] = $line->password;
}
array_push($accounts, [ array_push($accounts, [
'username' => $line->username, 'username' => $line->username,
'domain' => $request->get('domain'), 'domain' => $request->get('domain'),
@ -199,6 +204,31 @@ class AccountImportController extends Controller
$account->admin = true; $account->admin = true;
} }
// Set passwords
$passwordsToInsert = [];
$passwordAccounts = Account::whereIn('username', array_keys($passwords))
->where('domain', $request->get('domain'))
->get();
$algorithm = config('app.account_default_password_algorithm');
foreach ($passwordAccounts as $passwordAccount) {
array_push($passwordsToInsert, [
'account_id' => $passwordAccount->id,
'password' => bchash(
$passwordAccount->username,
config('app.account_realm') ?? $request->get('domain'),
$passwords[$passwordAccount->username],
$algorithm
),
'algorithm' => $algorithm
]);
}
Password::insert($passwordsToInsert);
// Set admins accounts // Set admins accounts
foreach ($phones as $username => $phone) { foreach ($phones as $username => $phone) {
$account = Account::where('username', $username) $account = Account::where('username', $username)

View file

@ -26,6 +26,9 @@ use Carbon\Carbon;
use App\AccountCreationToken; use App\AccountCreationToken;
use App\AccountCreationRequestToken; use App\AccountCreationRequestToken;
use App\Rules\PnParam;
use App\Rules\PnPrid;
use App\Rules\PnProvider;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Libraries\FlexisipPusherConnector; use App\Libraries\FlexisipPusherConnector;
@ -36,9 +39,9 @@ class CreationTokenController extends Controller
public function sendByPush(Request $request) public function sendByPush(Request $request)
{ {
$request->validate([ $request->validate([
'pn_provider' => 'required', 'pn_provider' => ['required', new PnProvider],
'pn_param' => 'required', 'pn_param' => [new PnParam],
'pn_prid' => 'required', 'pn_prid' => [new PnPrid],
]); ]);
$last = AccountCreationToken::where('pn_provider', $request->get('pn_provider')) $last = AccountCreationToken::where('pn_provider', $request->get('pn_provider'))

View file

@ -99,7 +99,12 @@ class AuthenticateJWT
return $next($request); return $next($request);
} }
if (!empty(config('app.account_authentication_bearer'))) { if (
!empty(config('app.account_authentication_bearer'))
// Bypass the JWT auth if we have an API Key
&& !$request->header('x-api-key')
&& !$request->cookie('x-api-key')
) {
$response = new Response(); $response = new Response();
$response->header( $response->header(

View file

@ -11,7 +11,7 @@ class ValidateJSON
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if ($request->expectsJson()) { if ($request->expectsJson() && !empty($request->getContent())) {
json_decode($request->getContent()); json_decode($request->getContent());
if (json_last_error() !== JSON_ERROR_NONE) { if (json_last_error() !== JSON_ERROR_NONE) {
abort(400, self::$message . ': ' . json_last_error_msg()); abort(400, self::$message . ': ' . json_last_error_msg());

View file

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

View file

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

View file

@ -0,0 +1,21 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Respect\Validation\Validator;
class PnProvider implements Rule
{
private $values = ['apns.dev', 'apns', 'fcm'];
public function passes($attribute, $value)
{
return in_array($value, $this->values);
}
public function message()
{
return 'The :attribute should be in ' . implode(', ', $this->values);
}
}

View file

@ -24,7 +24,7 @@
"react/socket": "^1.14", "react/socket": "^1.14",
"respect/validation": "^2.2", "respect/validation": "^2.2",
"sabre/vobject": "^4.5", "sabre/vobject": "^4.5",
"scyllaly/hcaptcha": "^4.4" "rvxlab/hcaptcha": "^4.4"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^3.9", "barryvdh/laravel-debugbar": "^3.9",

654
flexiapi/composer.lock generated

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -236,7 +236,6 @@ return [
Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class, Illuminate\View\ViewServiceProvider::class,
Scyllaly\HCaptcha\HCaptchaServiceProvider::class,
/* /*
* Package Service Providers... * Package Service Providers...

View file

@ -1,4 +1,4 @@
Username,Password,Role,Status,Phone,Email Username,Password,Role,Status,Phone,Email
john,number9,user,active,+12341234,john@lennon.com john,number9,user,active,+12341234,john@lennon.com
paul,a_day_in_the_life,admin,active,,paul@apple.com paul,a_day_in_the_life,admin,active,,paul@apple.com
ringo,allUneedIsL3ve,user,unactove,+123456,ringo@star.co.uk ringo,allUneedIsL3ve,user,unactive,+123456,ringo@star.co.uk
1 Username Password Role Status Phone Email
2 john number9 user active +12341234 john@lennon.com
3 paul a_day_in_the_life admin active paul@apple.com
4 ringo allUneedIsL3ve user unactove unactive +123456 ringo@star.co.uk

View file

@ -201,9 +201,9 @@ Return `503` if the token was not successfully sent.
JSON parameters: JSON parameters:
* `pn_provider` the push notification provider * `pn_provider` **required**, the push notification provider, must be in apns.dev, apns or fcm
* `pn_param` the push notification parameter * `pn_param` the push notification parameter, can be null or contain only alphanumeric and underscore characters
* `pn_prid` the push notification unique id * `pn_prid` the push notification unique id, can be null or contain only alphanumeric, dashes and colon characters
### `POST /account_creation_tokens/using-account-creation-request-token` ### `POST /account_creation_tokens/using-account-creation-request-token`
<span class="badge badge-success">Public</span> <span class="badge badge-success">Public</span>

View file

@ -1,7 +1,7 @@
@if (captchaConfigured()) @if (captchaConfigured())
<div class="large"> <div class="large">
<script src="https://hcaptcha.com/1/api.js?recaptchacompat=off" async="" defer=""></script> <script src="https://hcaptcha.com/1/api.js?recaptchacompat=off" async="" defer=""></script>
{!! HCaptcha::display() !!} <x-hcaptcha::widget />
@include('parts.errors', ['name' => 'h-captcha-response']) @include('parts.errors', ['name' => 'h-captcha-response'])
</div> </div>
@endif @endif

View file

@ -64,13 +64,20 @@ class ApiAccountApiKeyTest extends TestCase
->json($this->method, '/api/accounts/me') ->json($this->method, '/api/accounts/me')
->assertStatus(200); ->assertStatus(200);
$this->keyAuthenticated($account)
->json($this->method, '/api/accounts/me')
->assertStatus(200);
// Bypass the JWT middleware
config()->set('app.account_authentication_bearer', 'fake-bearer');
$this->keyAuthenticated($account) $this->keyAuthenticated($account)
->json($this->method, '/api/accounts/me') ->json($this->method, '/api/accounts/me')
->assertStatus(200); ->assertStatus(200);
$this->assertDatabaseHas('api_keys', [ $this->assertDatabaseHas('api_keys', [
'account_id' => $account->id, 'account_id' => $account->id,
'requests' => 2 'requests' => 3
]); ]);
DB::table('api_keys')->update(['ip' => 'no_localhost']); DB::table('api_keys')->update(['ip' => 'no_localhost']);

View file

@ -36,7 +36,7 @@ class ApiAccountCreationTokenTest extends TestCase
protected $adminRoute = '/api/account_creation_tokens'; protected $adminRoute = '/api/account_creation_tokens';
protected $method = 'POST'; protected $method = 'POST';
protected $pnProvider = 'provider'; protected $pnProvider = 'fcm';
protected $pnParam = 'param'; protected $pnParam = 'param';
protected $pnPrid = 'id'; protected $pnPrid = 'id';
@ -63,6 +63,27 @@ class ApiAccountCreationTokenTest extends TestCase
} }
public function testCorrectParameters() public function testCorrectParameters()
{ {
$this->assertSame(AccountCreationToken::count(), 0);
$this->json($this->method, $this->tokenRoute, [
'pn_provider' => 'wrong',
'pn_param' => $this->pnParam,
'pn_prid' => $this->pnPrid,
])->assertJsonValidationErrors(['pn_provider']);
$this->assertSame(AccountCreationToken::count(), 0);
$this->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider,
'pn_param' => '@wrong',
'pn_prid' => $this->pnPrid,
])->assertJsonValidationErrors(['pn_param']);
$this->assertSame(AccountCreationToken::count(), 0);
$this->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider,
'pn_param' => $this->pnParam,
'pn_prid' => '@wrong',
])->assertJsonValidationErrors(['pn_prid']);
$this->assertSame(AccountCreationToken::count(), 0); $this->assertSame(AccountCreationToken::count(), 0);
$this->json($this->method, $this->tokenRoute, [ $this->json($this->method, $this->tokenRoute, [
'pn_provider' => $this->pnProvider, 'pn_provider' => $this->pnProvider,
@ -173,8 +194,7 @@ class ApiAccountCreationTokenTest extends TestCase
'algorithm' => 'SHA-256', 'algorithm' => 'SHA-256',
'password' => '123', 'password' => '123',
'account_creation_token' => $token->token 'account_creation_token' => $token->token
])->assertStatus(422) ])->assertJsonValidationErrors(['account_creation_token']);
->assertJsonValidationErrors(['account_creation_token']);
} }
public function testBlacklistedUsername() public function testBlacklistedUsername()