diff --git a/flexiapi/.env.example b/flexiapi/.env.example
index f0ec0a4..e054df2 100644
--- a/flexiapi/.env.example
+++ b/flexiapi/.env.example
@@ -14,6 +14,7 @@ APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the generated API Keys are
# Risky toggles
APP_EVERYONE_IS_ADMIN=false # Allow any accounts to request the API as an administrator
APP_ADMINS_MANAGE_MULTI_DOMAINS=false # Allow admins to handle all the accounts in the database
+APP_DANGEROUS_ENDPOINTS=false # Enable some dangerous endpoints used for XMLRPC like fallback usage
# SIP server parameters
ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com # Proxy registrar address, can be different than the SIP domain
diff --git a/flexiapi/app/Exceptions/Handler.php b/flexiapi/app/Exceptions/Handler.php
index 49b5d1e..c5fa390 100644
--- a/flexiapi/app/Exceptions/Handler.php
+++ b/flexiapi/app/Exceptions/Handler.php
@@ -3,6 +3,7 @@
namespace App\Exceptions;
use Throwable;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
@@ -23,6 +24,10 @@ class Handler extends ExceptionHandler
public function render($request, Throwable $exception)
{
+ if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
+ return response()->json(['message' => 'Missing elements to perform the request'], 404);
+ }
+
return parent::render($request, $exception);
}
}
diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php
index 7e0a247..156a992 100644
--- a/flexiapi/app/Http/Controllers/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php
@@ -71,9 +71,7 @@ class AccountController extends Controller
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->display_name = $request->get('display_name');
- $account->domain = $request->has('domain') && config('app.admins_manage_multi_domains')
- ? $request->get('domain')
- : config('app.sip_domain');
+ $account->domain = $this->resolveDomain($request);
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');
diff --git a/flexiapi/app/Http/Controllers/Api/AccountController.php b/flexiapi/app/Http/Controllers/Api/AccountController.php
index 9c125b8..8f70f72 100644
--- a/flexiapi/app/Http/Controllers/Api/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Api/AccountController.php
@@ -23,15 +23,20 @@ use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Mail;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use App\Account;
use App\AccountTombstone;
use App\AccountCreationToken;
+use App\Alias;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
+use App\Libraries\OvhSMS;
+use App\Mail\RegisterConfirmation;
use App\Rules\IsNotPhoneNumber;
use App\Rules\NoUppercase;
+use App\Rules\WithoutSpaces;
class AccountController extends Controller
{
@@ -48,6 +53,164 @@ class AccountController extends Controller
]);
}
+ /**
+ * /!\ Dangerous endpoint, disabled by default
+ */
+ public function phoneInfo(Request $request, string $phone)
+ {
+ if (!config('app.dangerous_endpoints')) return abort(404);
+
+ $request->merge(['phone' => $phone]);
+ $request->validate([
+ 'phone' => [ 'required', new WithoutSpaces, 'starts_with:+']
+ ]);
+
+ $alias = Alias::where('alias', $phone)->first();
+ $account = $alias
+ ? $alias->account
+ : Account::sip($phone)->firstOrFail();
+
+ return \response()->json([
+ 'activated' => $account->activated,
+ 'realm' => $account->realm
+ ]);
+ }
+
+ /**
+ * /!\ Dangerous endpoint, disabled by default
+ * Store directly the account and alias in the DB and send a SMS or email for the validation
+ */
+ public function storePublic(Request $request)
+ {
+ if (!config('app.dangerous_endpoints')) return abort(404);
+
+ $request->validate([
+ 'username' => [
+ 'prohibits:phone',
+ new NoUppercase,
+ new IsNotPhoneNumber,
+ Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
+ $query->where('domain', $request->has('domain') ? $request->get('domain') : config('app.sip_domain'));
+ }),
+ Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) {
+ $query->where('domain', $request->has('domain') ? $request->get('domain') : config('app.sip_domain'));
+ }),
+ 'filled',
+ ],
+ 'algorithm' => 'required|in:SHA-256,MD5',
+ 'password' => 'required|filled',
+ 'domain' => 'min:3',
+ 'email' => 'required_without:phone|email',
+ 'phone' => [
+ 'required_without:email',
+ 'prohibits:username',
+ 'unique:aliases,alias',
+ 'unique:accounts,username',
+ new WithoutSpaces, 'starts_with:+'
+ ]
+ ]);
+
+ $account = new Account;
+ $account->username = !empty($request->get('username'))
+ ? $request->get('username')
+ : $request->get('phone');
+ $account->email = $request->get('email');
+ $account->activated = false;
+ $account->domain = $request->has('domain')
+ ? $request->get('domain')
+ : config('app.sip_domain');
+ $account->ip_address = $request->ip();
+ $account->creation_time = Carbon::now();
+ $account->user_agent = config('app.name');
+ $account->provisioning_token = Str::random(WebAuthenticateController::$emailCodeSize);
+ $account->save();
+
+ $account->updatePassword($request->get('password'), $request->get('algorithm'));
+
+ Log::channel('events')->info('API: Account created using the public endpoint', ['id' => $account->identifier]);
+
+ // Send validation by phone
+ if ($request->has('phone')) {
+ $alias = new Alias;
+ $alias->alias = $request->get('phone');
+ $alias->domain = config('app.sip_domain');
+ $alias->account_id = $account->id;
+ $alias->save();
+
+ $account->confirmation_key = generatePin();
+ $account->save();
+
+ Log::channel('events')->info('API: Account created using the public endpoint by phone', ['id' => $account->identifier]);
+
+ $ovhSMS = new OvhSMS;
+ $ovhSMS->send($request->get('phone'), 'Your ' . config('app.name') . ' recovery code is ' . $account->confirmation_key);
+ }
+
+ // Send validation by email
+ elseif ($request->has('email')) {
+ $account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize);
+ $account->save();
+
+ Log::channel('events')->info('API: Account created using the public endpoint by email', ['id' => $account->identifier]);
+
+ Mail::to($account)->send(new RegisterConfirmation($account));
+ }
+
+ // Full reload
+ return Account::withoutGlobalScopes()->find($account->id);
+ }
+
+ /**
+ * /!\ Dangerous endpoint, disabled by default
+ */
+ public function recoverByPhone(Request $request)
+ {
+ if (!config('app.dangerous_endpoints')) return abort(404);
+
+ $request->validate([
+ 'phone' => [
+ 'required', new WithoutSpaces, 'starts_with:+'
+ ]
+ ]);
+
+ $alias = Alias::where('alias', $request->get('phone'))->first();
+ $account = $alias
+ ? $alias->account
+ : Account::sip($request->get('phone'))->firstOrFail();
+
+ $account->confirmation_key = generatePin();
+ $account->save();
+
+ Log::channel('events')->info('API: Account recovery by phone', ['id' => $account->identifier]);
+
+ $ovhSMS = new OvhSMS;
+ $ovhSMS->send($request->get('phone'), 'Your ' . config('app.name') . ' recovery code is ' . $account->confirmation_key);
+ }
+
+ /**
+ * /!\ Dangerous endpoint, disabled by default
+ */
+ public function recoverUsingKey(string $sip, string $recoveryKey)
+ {
+ if (!config('app.dangerous_endpoints')) return abort(404);
+
+ $account = Account::sip($sip)
+ ->where('confirmation_key', $recoveryKey)
+ ->firstOrFail();
+
+ if ($account->activationExpired()) abort(403, 'Activation expired');
+
+ $account->activated = true;
+ $account->confirmation_key = null;
+ $account->save();
+
+ $account->passwords->each(function ($i, $k) {
+ $i->makeVisible(['password']);
+ });
+
+ return $account;
+ }
+
public function store(Request $request)
{
$request->validate([
@@ -56,21 +219,16 @@ class AccountController extends Controller
new NoUppercase,
new IsNotPhoneNumber,
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
- $query->where('domain', $request->has('domain') && config('app.everyone_is_admin') && config('app.admins_manage_multi_domains')
- ? $request->get('domain')
- : config('app.sip_domain'));
+ $query->where('domain', config('app.sip_domain'));
}),
Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) {
- $query->where('domain', $request->has('domain') && config('app.everyone_is_admin') && config('app.admins_manage_multi_domains')
- ? $request->get('domain')
- : config('app.sip_domain'));
+ $query->where('domain', config('app.sip_domain'));
}),
'filled',
],
'algorithm' => 'required|in:SHA-256,MD5',
'password' => 'required|filled',
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
- 'domain' => 'min:3',
'account_creation_token' => [
'required_without:token',
Rule::exists('account_creation_tokens', 'token')->where(function ($query) {
@@ -96,9 +254,7 @@ class AccountController extends Controller
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->activated = false;
- $account->domain = ($request->has('domain') && config('app.everyone_is_admin') && config('app.admins_manage_multi_domains'))
- ? $request->get('domain')
- : config('app.sip_domain');
+ $account->domain = config('app.sip_domain');
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');
diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
index 9f210d5..4677ba6 100644
--- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
@@ -111,10 +111,7 @@ class AccountController extends Controller
new NoUppercase,
new IsNotPhoneNumber,
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
- $query->where('domain', $request->has('domain') && config('app.admins_manage_multi_domains')
- ? $request->get('domain')
- : config('app.sip_domain')
- );
+ $query->where('domain', $this->resolveDomain($request));
}),
'filled',
],
@@ -139,15 +136,11 @@ class AccountController extends Controller
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->display_name = $request->get('display_name');
- $account->activated = $request->has('activated')
- ? (bool)$request->get('activated')
- : false;
+ $account->activated = $request->has('activated') ? (bool)$request->get('activated') : false;
$account->ip_address = $request->ip();
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->creation_time = Carbon::now();
- $account->domain = $request->has('domain') && config('app.admins_manage_multi_domains')
- ? $request->get('domain')
- : config('app.sip_domain');
+ $account->domain = $this->resolveDomain($request);
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
if (!$request->has('activated') || !(bool)$request->get('activated')) {
diff --git a/flexiapi/app/Http/Controllers/Controller.php b/flexiapi/app/Http/Controllers/Controller.php
index a0a2a8a..675ca4d 100644
--- a/flexiapi/app/Http/Controllers/Controller.php
+++ b/flexiapi/app/Http/Controllers/Controller.php
@@ -5,9 +5,20 @@ namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+
+ protected function resolveDomain(Request $request): string
+ {
+ return $request->has('domain')
+ && $request->user()
+ && $request->user()->isAdmin()
+ && config('app.admins_manage_multi_domains')
+ ? $request->get('domain')
+ : config('app.sip_domain');
+ }
}
diff --git a/flexiapi/app/Libraries/OvhSMS.php b/flexiapi/app/Libraries/OvhSMS.php
index 754ebc1..1311dc8 100644
--- a/flexiapi/app/Libraries/OvhSMS.php
+++ b/flexiapi/app/Libraries/OvhSMS.php
@@ -20,6 +20,7 @@
namespace App\Libraries;
use Ovh\Api;
+use Illuminate\Support\Facades\Log;
class OvhSMS
{
@@ -28,6 +29,10 @@ class OvhSMS
public function __construct()
{
+ if (empty(config('ovh.app_key'))) {
+ Log::error('OVH SMS API not configured');
+ }
+
$this->_api = new Api(
config('ovh.app_key'),
config('ovh.app_secret'),
@@ -35,12 +40,24 @@ class OvhSMS
config('ovh.app_consumer_key')
);
- $smsServices = $this->_api->get('/sms/');
- if (!empty($smsServices)) $this->_smsService = $smsServices[0];
+ try {
+ $smsServices = $this->_api->get('/sms/');
+
+ if (!empty($smsServices)) {
+ $this->_smsService = $smsServices[0];
+ }
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ Log::error('OVH SMS API not reachable: ' . $e->getMessage());
+ }
}
public function send(string $to, string $message)
{
+ if (!$this->_smsService) {
+ Log::error('OVH SMS API not configured');
+ return;
+ }
+
$content = (object) [
'charset' => 'UTF-8',
'class' => 'phoneDisplay',
@@ -54,9 +71,13 @@ class OvhSMS
'validityPeriod' => 2880
];
- $this->_api->post('/sms/'. $this->_smsService . '/jobs', $content);
- // One credit removed
+ try {
+ $this->_api->post('/sms/'. $this->_smsService . '/jobs', $content);
+ // One credit removed
- $this->_api->get('/sms/'. $this->_smsService . '/jobs');
+ $this->_api->get('/sms/'. $this->_smsService . '/jobs');
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ Log::error('OVH SMS not sent: ' . $e->getMessage());
+ }
}
}
\ No newline at end of file
diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock
index f3003da..94d02dc 100644
--- a/flexiapi/composer.lock
+++ b/flexiapi/composer.lock
@@ -326,16 +326,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.3.7",
+ "version": "3.3.8",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a"
+ "reference": "f873a820227bc352d023791775a01f078a30dfe1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/9f79d4650430b582f4598fe0954ef4d52fbc0a8a",
- "reference": "9f79d4650430b582f4598fe0954ef4d52fbc0a8a",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/f873a820227bc352d023791775a01f078a30dfe1",
+ "reference": "f873a820227bc352d023791775a01f078a30dfe1",
"shasum": ""
},
"require": {
@@ -350,14 +350,14 @@
"require-dev": {
"doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2022.1",
- "phpstan/phpstan": "1.7.13",
- "phpstan/phpstan-strict-rules": "^1.2",
- "phpunit/phpunit": "9.5.20",
- "psalm/plugin-phpunit": "0.16.1",
- "squizlabs/php_codesniffer": "3.7.0",
+ "phpstan/phpstan": "1.8.2",
+ "phpstan/phpstan-strict-rules": "^1.3",
+ "phpunit/phpunit": "9.5.21",
+ "psalm/plugin-phpunit": "0.17.0",
+ "squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
- "vimeo/psalm": "4.23.0"
+ "vimeo/psalm": "4.24.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
@@ -417,7 +417,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.3.7"
+ "source": "https://github.com/doctrine/dbal/tree/3.3.8"
},
"funding": [
{
@@ -433,7 +433,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-13T21:43:03+00:00"
+ "time": "2022-08-05T15:35:35+00:00"
},
{
"name": "doctrine/deprecations",
@@ -480,34 +480,31 @@
},
{
"name": "doctrine/event-manager",
- "version": "1.1.1",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
- "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f"
+ "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f",
- "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f",
+ "url": "https://api.github.com/repos/doctrine/event-manager/zipball/eb2ecf80e3093e8f3c2769ac838e27d8ede8e683",
+ "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
- "doctrine/common": "<2.9@dev"
+ "doctrine/common": "<2.9"
},
"require-dev": {
- "doctrine/coding-standard": "^6.0",
- "phpunit/phpunit": "^7.0"
+ "doctrine/coding-standard": "^9",
+ "phpstan/phpstan": "~1.4.10 || ^1.5.4",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.22"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "lib/Doctrine/Common"
@@ -554,7 +551,7 @@
],
"support": {
"issues": "https://github.com/doctrine/event-manager/issues",
- "source": "https://github.com/doctrine/event-manager/tree/1.1.x"
+ "source": "https://github.com/doctrine/event-manager/tree/1.1.2"
},
"funding": [
{
@@ -570,7 +567,7 @@
"type": "tidelift"
}
],
- "time": "2020-05-29T18:28:51+00:00"
+ "time": "2022-07-27T22:18:11+00:00"
},
{
"name": "doctrine/inflector",
@@ -1097,24 +1094,24 @@
},
{
"name": "graham-campbell/result-type",
- "version": "v1.0.4",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
- "reference": "0690bde05318336c7221785f2a932467f98b64ca"
+ "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca",
- "reference": "0690bde05318336c7221785f2a932467f98b64ca",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8",
+ "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8",
"shasum": ""
},
"require": {
- "php": "^7.0 || ^8.0",
- "phpoption/phpoption": "^1.8"
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9"
},
"require-dev": {
- "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8"
+ "phpunit/phpunit": "^8.5.28 || ^9.5.21"
},
"type": "library",
"autoload": {
@@ -1143,7 +1140,7 @@
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
- "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4"
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0"
},
"funding": [
{
@@ -1155,7 +1152,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-21T21:41:47+00:00"
+ "time": "2022-07-30T15:56:11+00:00"
},
{
"name": "guzzlehttp/guzzle",
@@ -1468,16 +1465,16 @@
},
{
"name": "laravel/framework",
- "version": "v8.83.22",
+ "version": "v8.83.23",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "96aecced5126d48e277e5339193c376fe82b6565"
+ "reference": "bdc707f8b9bcad289b24cd182d98ec7480ac4491"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/96aecced5126d48e277e5339193c376fe82b6565",
- "reference": "96aecced5126d48e277e5339193c376fe82b6565",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/bdc707f8b9bcad289b24cd182d98ec7480ac4491",
+ "reference": "bdc707f8b9bcad289b24cd182d98ec7480ac4491",
"shasum": ""
},
"require": {
@@ -1637,7 +1634,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2022-07-22T14:16:24+00:00"
+ "time": "2022-07-26T13:30:00+00:00"
},
{
"name": "laravel/serializable-closure",
@@ -2185,16 +2182,16 @@
},
{
"name": "nesbot/carbon",
- "version": "2.59.1",
+ "version": "2.61.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5"
+ "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
- "reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bdf4f4fe3a3eac4de84dbec0738082a862c68ba6",
+ "reference": "bdf4f4fe3a3eac4de84dbec0738082a862c68ba6",
"shasum": ""
},
"require": {
@@ -2283,7 +2280,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-29T21:43:55+00:00"
+ "time": "2022-08-06T12:41:24+00:00"
},
{
"name": "nikic/php-parser",
@@ -2509,29 +2506,33 @@
},
{
"name": "phpoption/phpoption",
- "version": "1.8.1",
+ "version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
- "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15"
+ "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15",
- "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
+ "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
"shasum": ""
},
"require": {
- "php": "^7.0 || ^8.0"
+ "php": "^7.2.5 || ^8.0"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.4.1",
- "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8"
+ "bamarni/composer-bin-plugin": "^1.8",
+ "phpunit/phpunit": "^8.5.28 || ^9.5.21"
},
"type": "library",
"extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": true
+ },
"branch-alias": {
- "dev-master": "1.8-dev"
+ "dev-master": "1.9-dev"
}
},
"autoload": {
@@ -2564,7 +2565,7 @@
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
- "source": "https://github.com/schmittjoh/php-option/tree/1.8.1"
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.0"
},
"funding": [
{
@@ -2576,7 +2577,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-04T23:24:31+00:00"
+ "time": "2022-07-30T15:51:26+00:00"
},
{
"name": "psr/cache",
@@ -2881,16 +2882,16 @@
},
{
"name": "psy/psysh",
- "version": "v0.11.7",
+ "version": "v0.11.8",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
- "reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a"
+ "reference": "f455acf3645262ae389b10e9beba0c358aa6994e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/77fc7270031fbc28f9a7bea31385da5c4855cb7a",
- "reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/f455acf3645262ae389b10e9beba0c358aa6994e",
+ "reference": "f455acf3645262ae389b10e9beba0c358aa6994e",
"shasum": ""
},
"require": {
@@ -2951,9 +2952,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
- "source": "https://github.com/bobthecow/psysh/tree/v0.11.7"
+ "source": "https://github.com/bobthecow/psysh/tree/v0.11.8"
},
- "time": "2022-07-07T13:49:11+00:00"
+ "time": "2022-07-28T14:25:11+00:00"
},
{
"name": "ralouphie/getallheaders",
@@ -3937,16 +3938,16 @@
},
{
"name": "symfony/console",
- "version": "v5.4.10",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000"
+ "reference": "535846c7ee6bc4dd027ca0d93220601456734b10"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000",
- "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000",
+ "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10",
+ "reference": "535846c7ee6bc4dd027ca0d93220601456734b10",
"shasum": ""
},
"require": {
@@ -4016,7 +4017,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.4.10"
+ "source": "https://github.com/symfony/console/tree/v5.4.11"
},
"funding": [
{
@@ -4032,20 +4033,20 @@
"type": "tidelift"
}
],
- "time": "2022-06-26T13:00:04+00:00"
+ "time": "2022-07-22T10:42:43+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v5.4.3",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "b0a190285cd95cb019237851205b8140ef6e368e"
+ "reference": "c1681789f059ab756001052164726ae88512ae3d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/b0a190285cd95cb019237851205b8140ef6e368e",
- "reference": "b0a190285cd95cb019237851205b8140ef6e368e",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d",
+ "reference": "c1681789f059ab756001052164726ae88512ae3d",
"shasum": ""
},
"require": {
@@ -4082,7 +4083,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v5.4.3"
+ "source": "https://github.com/symfony/css-selector/tree/v5.4.11"
},
"funding": [
{
@@ -4098,7 +4099,7 @@
"type": "tidelift"
}
],
- "time": "2022-01-02T09:53:40+00:00"
+ "time": "2022-06-27T16:58:25+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -4169,16 +4170,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v5.4.9",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "c116cda1f51c678782768dce89a45f13c949455d"
+ "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/c116cda1f51c678782768dce89a45f13c949455d",
- "reference": "c116cda1f51c678782768dce89a45f13c949455d",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/f75d17cb4769eb38cd5fccbda95cd80a054d35c8",
+ "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8",
"shasum": ""
},
"require": {
@@ -4220,7 +4221,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v5.4.9"
+ "source": "https://github.com/symfony/error-handler/tree/v5.4.11"
},
"funding": [
{
@@ -4236,7 +4237,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-21T13:57:48+00:00"
+ "time": "2022-07-29T07:37:50+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -4404,16 +4405,16 @@
},
{
"name": "symfony/finder",
- "version": "v5.4.8",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9"
+ "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9",
- "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c",
+ "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c",
"shasum": ""
},
"require": {
@@ -4447,7 +4448,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v5.4.8"
+ "source": "https://github.com/symfony/finder/tree/v5.4.11"
},
"funding": [
{
@@ -4463,20 +4464,20 @@
"type": "tidelift"
}
],
- "time": "2022-04-15T08:07:45+00:00"
+ "time": "2022-07-29T07:37:50+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v5.4.10",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e"
+ "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e7793b7906f72a8cc51054fbca9dcff7a8af1c1e",
- "reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389",
+ "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389",
"shasum": ""
},
"require": {
@@ -4520,7 +4521,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v5.4.10"
+ "source": "https://github.com/symfony/http-foundation/tree/v5.4.11"
},
"funding": [
{
@@ -4536,20 +4537,20 @@
"type": "tidelift"
}
],
- "time": "2022-06-19T13:13:40+00:00"
+ "time": "2022-07-20T13:00:38+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v5.4.10",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948"
+ "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/255ae3b0a488d78fbb34da23d3e0c059874b5948",
- "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee",
+ "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee",
"shasum": ""
},
"require": {
@@ -4632,7 +4633,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v5.4.10"
+ "source": "https://github.com/symfony/http-kernel/tree/v5.4.11"
},
"funding": [
{
@@ -4648,20 +4649,20 @@
"type": "tidelift"
}
],
- "time": "2022-06-26T16:57:59+00:00"
+ "time": "2022-07-29T12:30:22+00:00"
},
{
"name": "symfony/mime",
- "version": "v5.4.10",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "02265e1e5111c3cd7480387af25e82378b7ab9cc"
+ "reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/02265e1e5111c3cd7480387af25e82378b7ab9cc",
- "reference": "02265e1e5111c3cd7480387af25e82378b7ab9cc",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/3cd175cdcdb6db2e589e837dd46aff41027d9830",
+ "reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830",
"shasum": ""
},
"require": {
@@ -4715,7 +4716,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v5.4.10"
+ "source": "https://github.com/symfony/mime/tree/v5.4.11"
},
"funding": [
{
@@ -4731,7 +4732,7 @@
"type": "tidelift"
}
],
- "time": "2022-06-09T12:22:40+00:00"
+ "time": "2022-07-20T11:34:24+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -5552,16 +5553,16 @@
},
{
"name": "symfony/process",
- "version": "v5.4.8",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3"
+ "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
- "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
+ "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1",
+ "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1",
"shasum": ""
},
"require": {
@@ -5594,7 +5595,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v5.4.8"
+ "source": "https://github.com/symfony/process/tree/v5.4.11"
},
"funding": [
{
@@ -5610,20 +5611,20 @@
"type": "tidelift"
}
],
- "time": "2022-04-08T05:07:18+00:00"
+ "time": "2022-06-27T16:58:25+00:00"
},
{
"name": "symfony/routing",
- "version": "v5.4.8",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7"
+ "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/e07817bb6244ea33ef5ad31abc4a9288bef3f2f7",
- "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/3e01ccd9b2a3a4167ba2b3c53612762300300226",
+ "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226",
"shasum": ""
},
"require": {
@@ -5684,7 +5685,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v5.4.8"
+ "source": "https://github.com/symfony/routing/tree/v5.4.11"
},
"funding": [
{
@@ -5700,7 +5701,7 @@
"type": "tidelift"
}
],
- "time": "2022-04-18T21:45:37+00:00"
+ "time": "2022-07-20T13:00:38+00:00"
},
{
"name": "symfony/service-contracts",
@@ -5787,16 +5788,16 @@
},
{
"name": "symfony/string",
- "version": "v5.4.10",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097"
+ "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097",
- "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097",
+ "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322",
+ "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322",
"shasum": ""
},
"require": {
@@ -5853,7 +5854,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.10"
+ "source": "https://github.com/symfony/string/tree/v5.4.11"
},
"funding": [
{
@@ -5869,20 +5870,20 @@
"type": "tidelift"
}
],
- "time": "2022-06-26T15:57:47+00:00"
+ "time": "2022-07-24T16:15:25+00:00"
},
{
"name": "symfony/translation",
- "version": "v5.4.9",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca"
+ "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/1639abc1177d26bcd4320e535e664cef067ab0ca",
- "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21",
+ "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21",
"shasum": ""
},
"require": {
@@ -5950,7 +5951,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v5.4.9"
+ "source": "https://github.com/symfony/translation/tree/v5.4.11"
},
"funding": [
{
@@ -5966,7 +5967,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-06T12:33:37+00:00"
+ "time": "2022-07-20T13:00:38+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -6048,16 +6049,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v5.4.9",
+ "version": "v5.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "af52239a330fafd192c773795520dc2dd62b5657"
+ "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af52239a330fafd192c773795520dc2dd62b5657",
- "reference": "af52239a330fafd192c773795520dc2dd62b5657",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861",
+ "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861",
"shasum": ""
},
"require": {
@@ -6117,7 +6118,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v5.4.9"
+ "source": "https://github.com/symfony/var-dumper/tree/v5.4.11"
},
"funding": [
{
@@ -6133,7 +6134,7 @@
"type": "tidelift"
}
],
- "time": "2022-05-21T10:24:18+00:00"
+ "time": "2022-07-20T13:00:38+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -6558,16 +6559,16 @@
},
{
"name": "facade/flare-client-php",
- "version": "1.9.1",
+ "version": "1.10.0",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
- "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed"
+ "reference": "213fa2c69e120bca4c51ba3e82ed1834ef3f41b8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/facade/flare-client-php/zipball/b2adf1512755637d0cef4f7d1b54301325ac78ed",
- "reference": "b2adf1512755637d0cef4f7d1b54301325ac78ed",
+ "url": "https://api.github.com/repos/facade/flare-client-php/zipball/213fa2c69e120bca4c51ba3e82ed1834ef3f41b8",
+ "reference": "213fa2c69e120bca4c51ba3e82ed1834ef3f41b8",
"shasum": ""
},
"require": {
@@ -6580,7 +6581,7 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
- "phpunit/phpunit": "^7.5.16",
+ "phpunit/phpunit": "^7.5",
"spatie/phpunit-snapshot-assertions": "^2.0"
},
"type": "library",
@@ -6611,7 +6612,7 @@
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
- "source": "https://github.com/facade/flare-client-php/tree/1.9.1"
+ "source": "https://github.com/facade/flare-client-php/tree/1.10.0"
},
"funding": [
{
@@ -6619,7 +6620,7 @@
"type": "github"
}
],
- "time": "2021-09-13T12:16:46+00:00"
+ "time": "2022-08-09T11:23:57+00:00"
},
{
"name": "facade/ignition",
@@ -7324,252 +7325,25 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
- {
- "name": "phpdocumentor/reflection-common",
- "version": "2.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-2.x": "2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "support": {
- "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
- "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
- },
- "time": "2020-06-27T09:03:43+00:00"
- },
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "5.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
- "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
- "shasum": ""
- },
- "require": {
- "ext-filter": "*",
- "php": "^7.2 || ^8.0",
- "phpdocumentor/reflection-common": "^2.2",
- "phpdocumentor/type-resolver": "^1.3",
- "webmozart/assert": "^1.9.1"
- },
- "require-dev": {
- "mockery/mockery": "~1.3.2",
- "psalm/phar": "^4.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- },
- {
- "name": "Jaap van Otterdijk",
- "email": "account@ijaap.nl"
- }
- ],
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "support": {
- "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
- },
- "time": "2021-10-19T17:43:47+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "1.6.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "77a32518733312af16a44300404e945338981de3"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
- "reference": "77a32518733312af16a44300404e945338981de3",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0",
- "phpdocumentor/reflection-common": "^2.0"
- },
- "require-dev": {
- "ext-tokenizer": "*",
- "psalm/phar": "^4.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-1.x": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
- "support": {
- "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
- },
- "time": "2022-03-15T21:29:03+00:00"
- },
- {
- "name": "phpspec/prophecy",
- "version": "v1.15.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpspec/prophecy.git",
- "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
- "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.2",
- "php": "^7.2 || ~8.0, <8.2",
- "phpdocumentor/reflection-docblock": "^5.2",
- "sebastian/comparator": "^3.0 || ^4.0",
- "sebastian/recursion-context": "^3.0 || ^4.0"
- },
- "require-dev": {
- "phpspec/phpspec": "^6.0 || ^7.0",
- "phpunit/phpunit": "^8.0 || ^9.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Prophecy\\": "src/Prophecy"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
- },
- {
- "name": "Marcello Duarte",
- "email": "marcello.duarte@gmail.com"
- }
- ],
- "description": "Highly opinionated mocking framework for PHP 5.3+",
- "homepage": "https://github.com/phpspec/prophecy",
- "keywords": [
- "Double",
- "Dummy",
- "fake",
- "mock",
- "spy",
- "stub"
- ],
- "support": {
- "issues": "https://github.com/phpspec/prophecy/issues",
- "source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
- },
- "time": "2021-12-08T12:19:24+00:00"
- },
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.15",
+ "version": "9.2.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
+ "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
- "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073",
+ "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.13.0",
+ "nikic/php-parser": "^4.14",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@@ -7618,7 +7392,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16"
},
"funding": [
{
@@ -7626,7 +7400,7 @@
"type": "github"
}
],
- "time": "2022-03-07T09:28:20+00:00"
+ "time": "2022-08-20T05:26:47+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -7871,16 +7645,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.5.21",
+ "version": "9.5.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1"
+ "reference": "888556852e7e9bbeeedb9656afe46118765ade34"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1",
- "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34",
+ "reference": "888556852e7e9bbeeedb9656afe46118765ade34",
"shasum": ""
},
"require": {
@@ -7895,7 +7669,6 @@
"phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
"php": ">=7.3",
- "phpspec/prophecy": "^1.12.1",
"phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1",
@@ -7913,9 +7686,6 @@
"sebastian/type": "^3.0",
"sebastian/version": "^3.0.2"
},
- "require-dev": {
- "phpspec/prophecy-phpunit": "^2.0.1"
- },
"suggest": {
"ext-soap": "*",
"ext-xdebug": "*"
@@ -7957,7 +7727,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23"
},
"funding": [
{
@@ -7969,7 +7739,7 @@
"type": "github"
}
],
- "time": "2022-06-19T12:14:25+00:00"
+ "time": "2022-08-22T14:01:36+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php
index fddc407..c89d827 100644
--- a/flexiapi/config/app.php
+++ b/flexiapi/config/app.php
@@ -67,6 +67,11 @@ return [
*/
'admins_manage_multi_domains' => env('APP_ADMINS_MANAGE_MULTI_DOMAINS', false),
+ /**
+ * /!\ Enable dangerous endpoints required for fallback
+ */
+ 'dangerous_endpoints' => env('APP_DANGEROUS_ENDPOINTS', false),
+
/*
|--------------------------------------------------------------------------
| Application Environment
diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php
index dda650f..0c238f8 100644
--- a/flexiapi/resources/views/api/documentation_markdown.blade.php
+++ b/flexiapi/resources/views/api/documentation_markdown.blade.php
@@ -112,6 +112,26 @@ Return `404` if the token is non existing or invalid.
## Accounts
+### `POST /accounts/public`
+
+@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
+
+
+Public
+Unsecure endpoint
+Create an account.
+Return `422` if the parameters are invalid.
+Send an email with the activation key if `email` is set, send an SMS otherwise.
+
+JSON parameters:
+
+* `username` required if `phone` not set, unique username, minimum 6 characters
+* `password` required minimum 6 characters
+* `algorithm` required, values can be `SHA-256` or `MD5`
+* `domain` if not set the value is enforced to the default registration domain set in the global configuration
+* `email` optional if `phone` set, an email, set an email to the account
+* `phone` required if `username` not set, optional if `email` set, a phone number, set a phone number to the account
+
### `POST /accounts/with-account-creation-token`
Public
Create an account using an `account_creation_token`.
@@ -122,7 +142,6 @@ JSON parameters:
* `username` unique username, minimum 6 characters
* `password` required minimum 6 characters
* `algorithm` required, values can be `SHA-256` or `MD5`
-* `domain` **not configurable except during test deployments** the value is enforced to the default registration domain set in the global configuration
* `account_creation_token` the unique `account_creation_token`
* `dtmf_protocol` optional, values must be `sipinfo` or `rfc2833`
@@ -131,6 +150,39 @@ JSON parameters:
Retrieve public information about the account.
Return `404` if the account doesn't exists.
+### `GET /accounts/{phone}/info-by-phone`
+@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
+
+
+Public
+Unsecure endpoint
+Retrieve public information about the account.
+Return `404` if the account doesn't exists.
+
+### `POST /accounts/recover-by-phone`
+@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
+
+
+Public
+Unsecure endpoint
+Send a SMS with a recovery PIN code to the `phone` number provided.
+Return `404` if the account doesn't exists.
+
+JSON parameters:
+
+* `phone` required the phone number to send the SMS to
+
+### `GET /accounts/{sip}/recover/{recover_key}`
+@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
+
+
+Public
+Unsecure endpoint
+Activate the account if the correct `recover_key` is provided.
+Return the account information (including the hashed password) if valid.
+
+Return `404` if the account doesn't exists.
+
### `POST /accounts/{sip}/activate/email`
Public
Activate an account using a secret code received by email.
@@ -194,8 +246,7 @@ JSON parameters:
* `username` unique username, minimum 6 characters
* `password` required minimum 6 characters
* `algorithm` required, values can be `SHA-256` or `MD5`
-* `domain` **not configurable by default. The value is enforced to the default domain set in the global configuration (`app.sip_domain`)**
-The `domain` field is taken into account ONLY when `app.admins_manage_multi_domains` is set to `true` in the global configuration
+* `domain` **not configurable by default**. Only configurable if `APP_ADMINS_MANAGE_MULTI_DOMAINS` is set to `true` in the global configuration. Otherwise `APP_SIP_DOMAIN` is used.
* `activated` optional, a boolean, set to `false` by default
* `display_name` optional, string
* `admin` optional, a boolean, set to `false` by default, create an admin account
diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php
index c0dbb8d..2c08955 100644
--- a/flexiapi/routes/api.php
+++ b/flexiapi/routes/api.php
@@ -29,13 +29,22 @@ Route::get('ping', 'Api\PingController@ping');
Route::post('account_creation_tokens/send-by-push', 'Api\AccountCreationTokenController@sendByPush');
// Old URL, for retro-compatibility
Route::post('tokens', 'Api\AccountCreationTokenController@sendByPush');
+
Route::get('accounts/{sip}/info', 'Api\AccountController@info');
+
Route::post('accounts/with-account-creation-token', 'Api\AccountController@store');
// Old URL, for retro-compatibility
Route::post('accounts/with-token', 'Api\AccountController@store');
+
Route::post('accounts/{sip}/activate/email', 'Api\AccountController@activateEmail');
Route::post('accounts/{sip}/activate/phone', 'Api\AccountController@activatePhone');
+// /!\ Dangerous endpoints
+Route::post('accounts/public', 'Api\AccountController@storePublic');
+Route::get('accounts/{sip}/recover/{recovery_key}', 'Api\AccountController@recoverUsingKey');
+Route::post('accounts/recover-by-phone', 'Api\AccountController@recoverByPhone');
+Route::get('accounts/{phone}/info-by-phone', 'Api\AccountController@phoneInfo');
+
Route::post('accounts/auth_token', 'Api\AuthTokenController@store');
Route::get('accounts/me/api_key/{auth_token}', 'Api\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
diff --git a/flexiapi/tests/Feature/AccountApiTest.php b/flexiapi/tests/Feature/AccountApiTest.php
index d305313..e888c3c 100644
--- a/flexiapi/tests/Feature/AccountApiTest.php
+++ b/flexiapi/tests/Feature/AccountApiTest.php
@@ -24,7 +24,7 @@ use App\Account;
use App\AccountTombstone;
use App\ActivationExpiration;
use App\Admin;
-
+use App\Alias as AppAlias;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -480,6 +480,194 @@ class AccountApiTest extends TestCase
]);
}
+ /**
+ * /!\ Dangerous endpoints
+ */
+ public function testRecover()
+ {
+ $confirmationKey = '0123';
+ $password = Password::factory()->create();
+ $password->account->generateApiKey();
+ $password->account->confirmation_key = $confirmationKey;
+ $password->account->activated = false;
+ $password->account->save();
+
+ config()->set('app.dangerous_endpoints', true);
+
+ $this->assertDatabaseHas('accounts', [
+ 'username' => $password->account->username,
+ 'domain' => $password->account->domain,
+ 'activated' => false
+ ]);
+
+ $this->get($this->route.'/'.$password->account->identifier.'/recover/'.$confirmationKey)
+ ->assertJson(['passwords' => [[
+ 'password' => $password->password,
+ 'algorithm' => $password->algorithm
+ ]]])
+ ->assertStatus(200);
+
+ $this->json('GET', $this->route.'/'.$password->account->identifier.'/recover/'.$confirmationKey)
+ ->assertStatus(404);
+
+ $this->assertDatabaseHas('accounts', [
+ 'username' => $password->account->username,
+ 'domain' => $password->account->domain,
+ 'activated' => true
+ ]);
+ }
+
+ /**
+ * /!\ Dangerous endpoints
+ */
+ public function testRecoverPhone()
+ {
+ $phone = '+3361234';
+
+ $password = Password::factory()->create();
+ $password->account->generateApiKey();
+ $password->account->activated = false;
+ $password->account->save();
+
+ config()->set('app.dangerous_endpoints', true);
+
+ $alias = new AppAlias;
+ $alias->alias = $phone;
+ $alias->domain = $password->account->domain;
+ $alias->account_id = $password->account->id;
+ $alias->save();
+
+ $this->json($this->method, $this->route.'/recover-by-phone', [
+ 'phone' => $phone
+ ])
+ ->assertStatus(200);
+
+ $password->account->refresh();
+
+ $this->get($this->route.'/'.$password->account->identifier.'/recover/'.$password->account->confirmation_key)
+ ->assertStatus(200)
+ ->assertJson([
+ 'activated' => true
+ ]);
+
+ $this->get($this->route.'/'.$phone.'/info-by-phone')
+ ->assertStatus(200)
+ ->assertJson([
+ 'activated' => true
+ ]);
+
+ $this->get($this->route.'/+1234/info-by-phone')
+ ->assertStatus(404);
+
+ $this->json('GET', $this->route.'/'.$password->account->identifier.'/info-by-phone')
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['phone']);
+ }
+
+ /**
+ * /!\ Dangerous endpoints
+ */
+ public function testCreatePublic()
+ {
+ $username = 'publicuser';
+
+ config()->set('app.dangerous_endpoints', true);
+
+ // Missing email
+ $this->json($this->method, $this->route.'/public', [
+ 'username' => $username,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ ])
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['email']);
+
+ $this->json($this->method, $this->route.'/public', [
+ 'username' => $username,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(200)
+ ->assertJson([
+ 'activated' => false
+ ]);
+
+ // Already created
+ $this->json($this->method, $this->route.'/public', [
+ 'username' => $username,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['username']);
+
+ $this->assertDatabaseHas('accounts', [
+ 'username' => $username,
+ 'domain' => config('app.sip_domain')
+ ]);
+ }
+
+ public function testCreatePublicPhone()
+ {
+ $phone = '+12345';
+
+ config()->set('app.dangerous_endpoints', true);
+
+ // Username and phone
+ $this->json($this->method, $this->route.'/public', [
+ 'username' => 'myusername',
+ 'phone' => $phone,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['phone', 'username']);
+
+ // Bad phone format
+ $this->json($this->method, $this->route.'/public', [
+ 'phone' => 'username',
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['phone']);
+
+ $this->json($this->method, $this->route.'/public', [
+ 'phone' => $phone,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(200)
+ ->assertJson([
+ 'activated' => false
+ ]);
+
+ // Already exists
+ $this->json($this->method, $this->route.'/public', [
+ 'phone' => $phone,
+ 'algorithm' => 'SHA-256',
+ 'password' => '2',
+ 'email' => 'john@doe.tld',
+ ])
+ ->assertStatus(422)
+ ->assertJsonValidationErrors(['phone']);
+
+ $this->assertDatabaseHas('accounts', [
+ 'username' => $phone,
+ 'domain' => config('app.sip_domain')
+ ]);
+
+ $this->assertDatabaseHas('aliases', [
+ 'alias' => $phone,
+ 'domain' => config('app.sip_domain')
+ ]);
+ }
+
public function testActivatePhone()
{
$confirmationKey = '0123';
@@ -515,11 +703,11 @@ class AccountApiTest extends TestCase
])
->assertStatus(200);
- $this->get($this->route.'/'.$password->account->identifier.'/info')
- ->assertStatus(200)
- ->assertJson([
- 'activated' => true
- ]);
+ $this->assertDatabaseHas('accounts', [
+ 'username' => $password->account->username,
+ 'domain' => $password->account->domain,
+ 'activated' => true
+ ]);
}
public function testChangeEmail()
@@ -576,9 +764,7 @@ class AccountApiTest extends TestCase
'password' => $password
])
->assertStatus(422)
- ->assertJson([
- 'errors' => ['algorithm' => true]
- ]);
+ ->assertJsonValidationErrors(['algorithm']);
// Fresh password without an old one
$this->keyAuthenticated($account)
@@ -606,9 +792,7 @@ class AccountApiTest extends TestCase
'password' => $newPassword
])
->assertStatus(422)
- ->assertJson([
- 'errors' => ['old_password' => true]
- ]);
+ ->assertJsonValidationErrors(['old_password']);
// Set the new password with incorrect old password
$this->keyAuthenticated($account)
@@ -617,9 +801,7 @@ class AccountApiTest extends TestCase
'old_password' => 'blabla',
'password' => $newPassword
])
- ->assertJson([
- 'errors' => ['old_password' => true]
- ])
+ ->assertJsonValidationErrors(['old_password'])
->assertStatus(422);
// Set the new password