From b48c8f505d2200ce1025eb08972a25208cbf4076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Wed, 4 Aug 2021 16:48:25 +0200 Subject: [PATCH] Add a permanent provisioning URL, authenticated Complete the documentation Add a few tests for the provisioning urls Update the dependencies Bump the package version --- .../Account/ProvisioningController.php | 98 +++++++++++-------- flexiapi/composer.lock | 15 ++- .../database/factories/AccountFactory.php | 3 + .../api/documentation_markdown.blade.php | 12 ++- flexiapi/routes/web.php | 4 + .../tests/Feature/AccountProvisioningTest.php | 92 +++++++++++++++++ flexisip-account-manager.spec | 2 +- 7 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 flexiapi/tests/Feature/AccountProvisioningTest.php diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index e5d8795..ffa5052 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -51,7 +51,15 @@ class ProvisioningController extends Controller return response($result->getString())->header('Content-Type', $result->getMimeType()); } - public function show(Request $request, $confirmationKey = null) + /** + * Authenticated provisioning + */ + public function me(Request $request) + { + return $this->show($request, null, $request->user()); + } + + public function show(Request $request, $confirmationKey = null, Account $requestAccount = null) { // Load the hooks if they exists $provisioningHooks = config_path('provisioning_hooks.php'); @@ -99,67 +107,71 @@ class ProvisioningController extends Controller $account = null; // Account handling - if ($confirmationKey) { + if ($requestAccount) { + $account = $requestAccount; + } else if ($confirmationKey) { $account = Account::withoutGlobalScopes() ->where('confirmation_key', $confirmationKey) ->first(); + } - if ($account && !$account->activationExpired()) { + if ($account && !$account->activationExpired()) { + $section = $dom->createElement('section'); + $section->setAttribute('name', 'proxy_' . $proxyConfigIndex); + + $entry = $dom->createElement('entry', $account->identifier); + $entry->setAttribute('name', 'reg_identity'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', 1); + $entry->setAttribute('name', 'reg_sendregister'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', 'push_notification'); + $entry->setAttribute('name', 'refkey'); + $section->appendChild($entry); + + // Complete the section with the Proxy hook + if (function_exists('provisioningProxyHook')) { + provisioningProxyHook($section, $request, $account); + } + + $config->appendChild($section); + + $passwords = $account->passwords()->get(); + + foreach ($passwords as $password) { // => foreach ($passwords) $section = $dom->createElement('section'); - $section->setAttribute('name', 'proxy_' . $proxyConfigIndex); + $section->setAttribute('name', 'auth_info_' . $authInfoIndex); $entry = $dom->createElement('entry', $account->identifier); - $entry->setAttribute('name', 'reg_identity'); + $entry->setAttribute('name', 'username'); $section->appendChild($entry); - $entry = $dom->createElement('entry', 1); - $entry->setAttribute('name', 'reg_sendregister'); + $entry = $dom->createElement('entry', $password->password); + $entry->setAttribute('name', 'ha1'); $section->appendChild($entry); - $entry = $dom->createElement('entry', 'push_notification'); - $entry->setAttribute('name', 'refkey'); + $entry = $dom->createElement('entry', $account->resolvedRealm); + $entry->setAttribute('name', 'realm'); $section->appendChild($entry); - // Complete the section with the Proxy hook - if (function_exists('provisioningProxyHook')) { - provisioningProxyHook($section, $request, $account); + $entry = $dom->createElement('entry', $password->algorithm); + $entry->setAttribute('name', 'algorithm'); + $section->appendChild($entry); + + // Complete the section with the Auth hook + if (function_exists('provisioningAuthHook')) { + provisioningAuthHook($section, $request, $password); } $config->appendChild($section); - $passwords = $account->passwords()->get(); + $authInfoIndex++; - foreach ($passwords as $password) { // => foreach ($passwords) - $section = $dom->createElement('section'); - $section->setAttribute('name', 'auth_info_' . $authInfoIndex); - - $entry = $dom->createElement('entry', $account->identifier); - $entry->setAttribute('name', 'username'); - $section->appendChild($entry); - - $entry = $dom->createElement('entry', $password->password); - $entry->setAttribute('name', 'ha1'); - $section->appendChild($entry); - - $entry = $dom->createElement('entry', $account->resolvedRealm); - $entry->setAttribute('name', 'realm'); - $section->appendChild($entry); - - $entry = $dom->createElement('entry', $password->algorithm); - $entry->setAttribute('name', 'algorithm'); - $section->appendChild($entry); - - // Complete the section with the Auth hook - if (function_exists('provisioningAuthHook')) { - provisioningAuthHook($section, $request, $password); - } - - $config->appendChild($section); - - $authInfoIndex++; - - } + } + if ($confirmationKey) { $account->confirmation_key = null; $account->save(); } diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock index f6b8a5f..6f5147f 100644 --- a/flexiapi/composer.lock +++ b/flexiapi/composer.lock @@ -979,16 +979,16 @@ }, { "name": "laravel/framework", - "version": "v8.52.0", + "version": "v8.53.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "8fe9877d52e25f8aed36c51734e5a8510be967e6" + "reference": "4b2e3e7317da82dd9f5b88d477abd93444748b43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/8fe9877d52e25f8aed36c51734e5a8510be967e6", - "reference": "8fe9877d52e25f8aed36c51734e5a8510be967e6", + "url": "https://api.github.com/repos/laravel/framework/zipball/4b2e3e7317da82dd9f5b88d477abd93444748b43", + "reference": "4b2e3e7317da82dd9f5b88d477abd93444748b43", "shasum": "" }, "require": { @@ -1061,7 +1061,7 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", + "aws/aws-sdk-php": "^3.186.4", "doctrine/dbal": "^2.6|^3.0", "filp/whoops": "^2.8", "guzzlehttp/guzzle": "^6.5.5|^7.0.1", @@ -1074,7 +1074,7 @@ "symfony/cache": "^5.1.4" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.186.4).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", "ext-ftp": "Required to use the Flysystem FTP driver.", @@ -1143,7 +1143,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-07-27T13:03:29+00:00" + "time": "2021-08-03T14:36:33+00:00" }, { "name": "laravel/tinker", @@ -7493,7 +7493,6 @@ "type": "github" } ], - "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { diff --git a/flexiapi/database/factories/AccountFactory.php b/flexiapi/database/factories/AccountFactory.php index f63f0ba..c378b33 100644 --- a/flexiapi/database/factories/AccountFactory.php +++ b/flexiapi/database/factories/AccountFactory.php @@ -20,6 +20,8 @@ namespace Database\Factories; use App\Account; +use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; +use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Factories\Factory; class AccountFactory extends Factory @@ -33,6 +35,7 @@ class AccountFactory extends Factory 'domain' => config('app.sip_domain'), 'email' => $this->faker->email, 'user_agent' => $this->faker->userAgent, + 'confirmation_key' => Str::random(WebAuthenticateController::$emailCodeSize), 'ip_address' => $this->faker->ipv4, 'creation_time' => $this->faker->dateTime, 'activated' => true diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index 20a2a9b..2dced3d 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -192,9 +192,17 @@ When an account is having an available `confirmation_key` it can be provisioned Those two URL are not API endpoints, they are not located under `/api`. -### `VISIT /provisioning/{confirmation_key}` +### `VISIT /provisioning/` Return the provisioning information available in the liblinphone configuration file (if correctly configured). -If the `confirmation_key` is valid the related account information are added to the returned XML. The account is then considered as "provisioned" and those account related information will be removed in the upcoming requests. + +### `VISIT /provisioning/{confirmation_key}` +Return the provisioning information available in the liblinphone configuration file. +If the `confirmation_key` is valid the related account information are added to the returned XML. The account is then considered as "provisioned" and those account related information will be removed in the upcoming requests (the content will be the same as the previous url). ### `VISIT /provisioning/qrcode/{confirmation_key}` Return a QRCode that points to the provisioning URL. + +## Authenticated provisioning + +### `VISIT /provisioning/me` +Return the same base content as the previous URL and the account related information, similar to the `confirmation_key` endpoint. However this endpoint will always return those information. \ No newline at end of file diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index c59134a..d6ada5b 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -32,6 +32,10 @@ Route::get('login/phone', 'Account\AuthenticateController@loginPhone')->name('ac Route::post('authenticate/phone', 'Account\AuthenticateController@authenticatePhone')->name('account.authenticate.phone'); Route::post('authenticate/phone/confirm', 'Account\AuthenticateController@validatePhone')->name('account.authenticate.phone_confirm'); +Route::group(['middleware' => 'auth.digest_or_key'], function () { + Route::get('provisioning/me', 'Account\ProvisioningController@me')->name('provisioning.me'); +}); + Route::get('provisioning/qrcode/{confirmation}', 'Account\ProvisioningController@qrcode')->name('provisioning.qrcode'); Route::get('provisioning/{confirmation?}', 'Account\ProvisioningController@show')->name('provisioning.show'); diff --git a/flexiapi/tests/Feature/AccountProvisioningTest.php b/flexiapi/tests/Feature/AccountProvisioningTest.php new file mode 100644 index 0000000..ca9eb23 --- /dev/null +++ b/flexiapi/tests/Feature/AccountProvisioningTest.php @@ -0,0 +1,92 @@ +. +*/ + +namespace Tests\Feature; + +use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\TestCase; + +use App\Password; + +class AccountProvisioningTest extends TestCase +{ + use RefreshDatabase; + + protected $route = '/provisioning'; + protected $accountRoute = '/provisioning/me'; + protected $method = 'GET'; + + protected $pnProvider = 'provider'; + protected $pnParam = 'param'; + protected $pnPrid = 'id'; + + public function testBaseProvisioning() + { + $response = $this->get($this->route); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/xml'); + $response->assertDontSee('ha1'); + } + + public function testAuthenticatedProvisioning() + { + $response = $this->get($this->accountRoute); + $response->assertStatus(302); + + $password = Password::factory()->create(); + $password->account->generateApiKey(); + + // Ensure that we get the authentication password once + $response = $this->keyAuthenticated($password->account) + ->get($this->accountRoute) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee('ha1'); + + // And then twice + $response = $this->keyAuthenticated($password->account) + ->get($this->accountRoute) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee('ha1'); + } + + public function testConfirmationKeyProvisioning() + { + $response = $this->get($this->route.'/1234'); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/xml'); + $response->assertDontSee('ha1'); + + $password = Password::factory()->create(); + $password->account->generateApiKey(); + + // Ensure that we get the authentication password once + $response = $this->get($this->route.'/'.$password->account->confirmation_key) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee('ha1'); + + // And then twice + $response = $this->get($this->route.'/'.$password->account->confirmation_key) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertDontSee('ha1'); + } +} \ No newline at end of file diff --git a/flexisip-account-manager.spec b/flexisip-account-manager.spec index cbf9d20..f1da2cb 100644 --- a/flexisip-account-manager.spec +++ b/flexisip-account-manager.spec @@ -8,7 +8,7 @@ #%define _datadir %{_datarootdir} #%define _docdir %{_datadir}/doc -%define build_number 94 +%define build_number 95 %define var_dir /var/opt/belledonne-communications %define opt_dir /opt/belledonne-communications/share/flexisip-account-manager