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