mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 01:58:07 +00:00
Draft: Private Fix FLEXIAPI-408 Alstom hooks
This commit is contained in:
parent
4d601c4a9c
commit
a20c0cff9c
2 changed files with 267 additions and 0 deletions
144
flexiapi/config/provisioning_hooks.php.alstom
Normal file
144
flexiapi/config/provisioning_hooks.php.alstom
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
use App\Account;
|
||||
use App\Password;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Token\Parser;
|
||||
|
||||
define('ASTERISK_PATH_CERFIFICATE', false /* or '/path/to/self-signed/cert.pem' */);
|
||||
define('ASTERISK_ARI_ROOT_URL', 'http://ari.asterisk.org:8088/ari/endpoints/');
|
||||
|
||||
/**
|
||||
* This file contains hooks functions used by the provisioning query
|
||||
* Check the commented code to have an overview of what can be done using the parameters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Complete the proxy section XML node
|
||||
* @param DOMElement $proxySection
|
||||
* @param Request $request
|
||||
* @param Account $account
|
||||
* @return void
|
||||
*/
|
||||
function provisioningProxyHook(\DOMElement $proxySection, Request $request, Account $account)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Complete a Auth section XML node
|
||||
* @param DOMElement $proxySection
|
||||
* @param Request $request
|
||||
* @param Password $password
|
||||
* @return void
|
||||
*/
|
||||
function provisioningAuthHook(\DOMElement $authSection, Request $request, Password $password)
|
||||
{
|
||||
$token = (new Parser(new JoseEncoder()))->parse($request->bearerToken());
|
||||
|
||||
if ($token->claims()->has('matching_accounts')) {
|
||||
$matchingAccounts = $token->claims()->get('matching_accounts');
|
||||
if (
|
||||
is_array($matchingAccounts)
|
||||
&& !empty($matchingAccounts)
|
||||
) {
|
||||
$firstMatchingAccount = array_shift($matchingAccounts);
|
||||
|
||||
if (\str_contains($firstMatchingAccount, '@')) {
|
||||
list($username, $domain) = explode('@', substr($firstMatchingAccount, 4));
|
||||
|
||||
$accounts = Account::withoutGlobalScopes()->where(['username' => $username, 'domain' => $domain]);
|
||||
|
||||
foreach ($matchingAccounts as $sip) {
|
||||
if (\str_contains($sip, '@')) {
|
||||
list($username, $domain) = explode('@', substr($sip, 4));
|
||||
$accounts = $accounts->orWhere(
|
||||
fn($query) => $query
|
||||
->where('username', $username)
|
||||
->where('domain', $domain)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$accounts = $accounts->get();
|
||||
|
||||
if ($accounts->count() == count($matchingAccounts) + 1) {
|
||||
// Resolving the first Asterisk offline account from the list
|
||||
$resolvedAccount = null;
|
||||
|
||||
$resolvedAccount = $accounts->first();
|
||||
/* foreach ($accounts as $account) {
|
||||
$response = Http::withOptions([
|
||||
'verify' => ASTERISK_PATH_CERFIFICATE
|
||||
])->get(ASTERISK_ARI_ROOT_URL . 'PJSIP/' . $account->identifier); // account SIP address
|
||||
|
||||
if ($response->json('state') == 'offline') {
|
||||
$resolvedAccount = $account;
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
if ($resolvedAccount) {
|
||||
Log::channel('events')->info(
|
||||
'Alstom Account: Account provisioned',
|
||||
['id' => $resolvedAccount->identifier]
|
||||
);
|
||||
|
||||
$xpath = new \DOMXpath($authSection->ownerDocument);
|
||||
|
||||
$xpath->query("//entry[@name='reg_identity']")->item(0)->nodeValue = $resolvedAccount->fullIdentifier;
|
||||
|
||||
$xpath->query("//entry[@name='username']")->item(0)->nodeValue = $resolvedAccount->username;
|
||||
$xpath->query("//entry[@name='domain']")->item(0)->nodeValue = $resolvedAccount->domain;
|
||||
|
||||
$password = $resolvedAccount->passwords()->first();
|
||||
|
||||
$xpath->query("//entry[@name='ha1']")->item(0)->nodeValue = $password->password;
|
||||
$xpath->query("//entry[@name='realm']")->item(0)->nodeValue = $resolvedAccount->resolvedRealm;
|
||||
$xpath->query("//entry[@name='algorithm']")->item(0)->nodeValue = $password->algorithm;
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('events')->info(
|
||||
'Alstom Account: No account can be provisioned',
|
||||
['id' => $token->claims()->get('matching_accounts')]
|
||||
);
|
||||
abort(404, 'No account can be provisioned');
|
||||
}
|
||||
|
||||
Log::channel('events')->info(
|
||||
'Alstom Account: No account can be provisioned',
|
||||
['id' => $token->claims()->get('matching_accounts')]
|
||||
);
|
||||
abort(400, 'Listed matching_accounts are not present in the database');
|
||||
}
|
||||
|
||||
abort(400, 'Invalid matching_accounts format');
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('events')->info(
|
||||
'Alstom Account: matching_accounts is empty or invalid'
|
||||
);
|
||||
abort(400, 'matching_accounts is empty or invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('events')->info(
|
||||
'Alstom Account: matching_accounts element missing'
|
||||
);
|
||||
abort(400, 'matching_accounts element missing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Complete the proxy section XML node, the Account might be passed as a parameter if resolved
|
||||
* @param DOMElement $proxySection
|
||||
* @param Request $request
|
||||
* @param Account $account
|
||||
* @return void
|
||||
*/
|
||||
function provisioningAdditionalSectionHook(\DOMElement $config, Request $request, ?Account $account)
|
||||
{
|
||||
}
|
||||
123
flexiapi/tests/Feature/AccountAlstomTest.php
Normal file
123
flexiapi/tests/Feature/AccountAlstomTest.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2021 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Password;
|
||||
use DateTimeImmutable;
|
||||
use Lcobucci\Clock\FrozenClock;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\JwtFacade;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
|
||||
class AccountAlstomTest extends AccountJWTAuthenticationTest
|
||||
{
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testAlstomProvisioning()
|
||||
{
|
||||
# JWT is disabled if Sodium is not loaded
|
||||
if (!extension_loaded('sodium'))
|
||||
return;
|
||||
|
||||
$password = Password::factory()->create();
|
||||
|
||||
//$domain = 'sip_provisioning.example.com';
|
||||
$bearer = 'authz_server="https://sso.test/", realm="sip.test.org"';
|
||||
|
||||
//\App\Space::where('domain', $password->account->domain)->update(['host' => $domain]);
|
||||
//config()->set('app.sip_domain', $domain);
|
||||
config()->set('services.jwt.rsa_public_key_pem', $this->serverPublicKeyPem);
|
||||
|
||||
$this->get($this->route)->assertStatus(400);
|
||||
|
||||
// Accounts to provision
|
||||
$passwordAccount1 = Password::factory()->create();
|
||||
$passwordAccount2 = Password::factory()->create();
|
||||
|
||||
$clock = new FrozenClock(new DateTimeImmutable());
|
||||
|
||||
config()->set('services.jwt.sip_identifier', 'sip_identity');
|
||||
|
||||
$token = (new JwtFacade(null, $clock))->issue(
|
||||
new Sha256(),
|
||||
InMemory::plainText($this->serverPrivateKeyPem),
|
||||
static fn(
|
||||
Builder $builder,
|
||||
DateTimeImmutable $issuedAt
|
||||
): Builder => $builder
|
||||
->withClaim(
|
||||
'sip_identity',
|
||||
'sip:' . $password->account->username . '@' . $password->account->domain
|
||||
)
|
||||
->withClaim(
|
||||
'matching_accounts',
|
||||
[
|
||||
'sip:' . $passwordAccount1->account->identifier,
|
||||
'sip:' . $passwordAccount2->account->identifier
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token->toString(),
|
||||
'x-linphone-provisioning' => true,
|
||||
])
|
||||
->get($this->accountRoute)
|
||||
->assertStatus(200)
|
||||
->assertSee($passwordAccount1->account->username)
|
||||
->assertSee($passwordAccount1->account->passwords()->first()->ha1);
|
||||
|
||||
// Non existing accounts
|
||||
|
||||
$token = (new JwtFacade(null, $clock))->issue(
|
||||
new Sha256(),
|
||||
InMemory::plainText($this->serverPrivateKeyPem),
|
||||
static fn(
|
||||
Builder $builder,
|
||||
DateTimeImmutable $issuedAt
|
||||
): Builder => $builder
|
||||
->withClaim(
|
||||
'sip_identity',
|
||||
'sip:' . $password->account->username . '@' . $password->account->domain
|
||||
)
|
||||
->withClaim(
|
||||
'matching_accounts',
|
||||
[
|
||||
'sip:' . $passwordAccount1->account->identifier,
|
||||
'sip:' . $passwordAccount2->account->identifier,
|
||||
'sip:other@account.com'
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token->toString(),
|
||||
'x-linphone-provisioning' => true,
|
||||
])
|
||||
->get($this->accountRoute)
|
||||
->assertStatus(400)
|
||||
->dump();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue