From 12ef6d472ea07348ed9e40f1bda5ba451f23bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Thu, 19 Sep 2024 17:19:39 +0200 Subject: [PATCH] Fix FLEXIAPI-212 Add CoTURN credentials support in the provisioning --- CHANGELOG.md | 1 + flexiapi/.env.example | 7 ++ .../Account/ProvisioningController.php | 83 +++++++++++++++++-- flexiapi/config/app.php | 8 ++ flexiapi/config/rcfile | 5 -- .../tests/Feature/AccountProvisioningTest.php | 41 ++++++++- 6 files changed, 134 insertions(+), 11 deletions(-) delete mode 100644 flexiapi/config/rcfile diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da9d03..f56d6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ v1.6 - Fix FLEXIAPI-203 Implement domain based Linphone configuration, add documentation, complete API endpoints, complete provisioning XML - Fix FLEXIAPI-208 Add SMS templates documentation - Fix FLEXIAPI-211 Add a JSON validation middleware + test +- Fix FLEXIAPI-212 Add CoTURN credentials support in the provisioning v1.5 --- diff --git a/flexiapi/.env.example b/flexiapi/.env.example index 4d7ab4d..28be017 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -106,6 +106,13 @@ MAIL_VERIFY_PEER=true MAIL_VERIFY_PEER_NAME=true MAIL_SIGNATURE="The Example Team" +# CoTURN + +COTURN_SERVER_HOST= # IP or domain name +COTURN_SESSION_TTL_MINUTES=1440 # 60 * 24 +COTURN_STATIC_AUTH_SECRET= # static-auth-secret in the coturn configuration +COTURN_REALM= # realm in the coturn configuration, empty by default + # OVH SMS API variables OVH_APP_KEY= OVH_APP_SECRET= diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index 3d8a38a..113adcf 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -50,7 +50,9 @@ class ProvisioningController extends Controller }) ->firstOrFail(); - if ($account->activationExpired()) abort(404); + if ($account->activationExpired()) { + abort(404); + } $params = ['provisioning_token' => $provisioningToken]; @@ -235,6 +237,7 @@ class ProvisioningController extends Controller if ($section == null) { $section = $dom->createElement('section'); $section->setAttribute('name', 'proxy_0'); + $config->appendChild($section); } $entry = $dom->createElement('entry', $account->fullIdentifier); @@ -246,17 +249,89 @@ class ProvisioningController extends Controller provisioningProxyHook($section, $request, $account); } - $config->appendChild($section); - $passwords = $account->passwords()->get(); $authInfoIndex = 0; + // CoTURN + if (config('app.coturn_session_ttl_minutes') > 0 + && !empty(config('app.coturn_server_host')) + && !empty(config('app.coturn_static_auth_secret'))) { + $user = 'foo'; + $secret = config('app.coturn_static_auth_secret'); + + $ttl = config('app.coturn_session_ttl_minutes') * 60; + $time = time() + $ttl; + $username = $time . ':' . Str::random(16); + $password = base64_encode(hash_hmac('sha1', $username, $secret, true)); + + // net + $section = $xpath->query("//section[@name='net']")->item(0); + + if ($section == null) { + $section = $dom->createElement('section'); + $section->setAttribute('name', 'net'); + $config->appendChild($section); + } + + $ref = Str::random(8); + + $entry = $dom->createElement('entry', $ref); + $entry->setAttribute('name', 'nat_policy_ref'); + $section->appendChild($entry); + + // nat_policy_0 + $section = $dom->createElement('section'); + $section->setAttribute('name', 'nat_policy_0'); + $config->appendChild($section); + + $entry = $dom->createElement('entry', $ref); + $entry->setAttribute('name', 'ref'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', config('app.coturn_server_host')); + $entry->setAttribute('name', 'stun_server'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $username); + $entry->setAttribute('name', 'stun_server_username'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', 'turn,ice'); + $entry->setAttribute('name', 'protocols'); + $section->appendChild($entry); + + // auth_info_x + $section = $xpath->query("//section[@name='auth_info_" . $authInfoIndex . "']")->item(0); + + if ($section == null) { + $section = $dom->createElement('section'); + $section->setAttribute('name', 'auth_info_' . $authInfoIndex); + $config->appendChild($section); + $authInfoIndex++; + } + + $entry = $dom->createElement('entry', $username); + $entry->setAttribute('name', 'username'); + $section->appendChild($entry); + + $entry = $dom->createElement('entry', $password); + $entry->setAttribute('name', 'passwd'); + $section->appendChild($entry); + + if (!empty(config('app.coturn_realm'))) { + $entry = $dom->createElement('entry', config('app.coturn_realm')); + $entry->setAttribute('name', 'realm'); + $section->appendChild($entry); + } + } + foreach ($passwords as $password) { $section = $xpath->query("//section[@name='auth_info_" . $authInfoIndex . "']")->item(0); if ($section == null) { $section = $dom->createElement('section'); $section->setAttribute('name', 'auth_info_' . $authInfoIndex); + $config->appendChild($section); } $entry = $dom->createElement('entry', $account->username); @@ -284,8 +359,6 @@ class ProvisioningController extends Controller provisioningAuthHook($section, $request, $password); } - $config->appendChild($section); - $authInfoIndex++; } } diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index 68a613e..495bbc7 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -50,6 +50,14 @@ return [ */ 'account_creation_token_retry_minutes' => env('APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES', 60), + /** + * CoTURN authentication in the provisioning + */ + 'coturn_server_host' => env('COTURN_SERVER_HOST', null), + 'coturn_session_ttl_minutes' => (int)env('COTURN_SESSION_TTL_MINUTES', 60 * 24), + 'coturn_static_auth_secret' => env('COTURN_STATIC_AUTH_SECRET', null), + 'coturn_realm' => env('COTURN_REALM', null), + /** * External interfaces */ diff --git a/flexiapi/config/rcfile b/flexiapi/config/rcfile deleted file mode 100644 index d76bcd4..0000000 --- a/flexiapi/config/rcfile +++ /dev/null @@ -1,5 +0,0 @@ -[auth_info_0] -test=foobar - -[auth_info_1] -blabla=gnap diff --git a/flexiapi/tests/Feature/AccountProvisioningTest.php b/flexiapi/tests/Feature/AccountProvisioningTest.php index 163e957..feab65a 100644 --- a/flexiapi/tests/Feature/AccountProvisioningTest.php +++ b/flexiapi/tests/Feature/AccountProvisioningTest.php @@ -95,7 +95,7 @@ class AccountProvisioningTest extends TestCase ])->get($this->accountRoute)->assertStatus(302); } - public function testAuthenticatedProvisioning() + public function testAuthenticatedWithPasswordProvisioning() { $password = Password::factory()->create(); $password->account->generateApiKey(); @@ -342,4 +342,43 @@ class AccountProvisioningTest extends TestCase ->get($this->route . '/' . $account->provisioning_token) ->assertStatus(410); } + + public function testCoTURN() + { + $account = Account::factory()->create(); + $account->generateApiKey(); + + $host = 'coturn.tld'; + $realm = 'realm.tld'; + + config()->set('app.coturn_server_host', $host); + config()->set('app.coturn_static_auth_secret', 'secret'); + + $response = $this->withHeaders([ + 'x-linphone-provisioning' => true, + ]) + ->keyAuthenticated($account) + ->get($this->accountRoute) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee($host) + ->assertSee('nat_policy_ref') + ->assertSee('stun_server_username') + ->assertSee('nat_policy_0') + ->assertDontSee('realm') + ->assertDontSee($realm); + + config()->set('app.coturn_realm', $realm); + + $response = $this->withHeaders([ + 'x-linphone-provisioning' => true, + ]) + ->keyAuthenticated($account) + ->get($this->accountRoute) + ->assertStatus(200) + ->assertHeader('Content-Type', 'application/xml') + ->assertSee($host) + ->assertSee('realm') + ->assertSee($realm); + } }