370 lines
13 KiB
PHP
370 lines
13 KiB
PHP
<?php
|
|
/*
|
|
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
|
Copyright (C) 2020 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 App\Http\Controllers\Account;
|
|
|
|
use App\Account;
|
|
use App\AuthToken;
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Str;
|
|
|
|
use Endroid\QrCode\Builder\Builder;
|
|
use Endroid\QrCode\Encoding\Encoding;
|
|
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
|
|
use Endroid\QrCode\Writer\PngWriter;
|
|
|
|
class ProvisioningController extends Controller
|
|
{
|
|
public function documentation(Request $request)
|
|
{
|
|
return view('provisioning.documentation', [
|
|
'documentation' => markdownDocumentationView('provisioning.documentation_markdown')
|
|
]);
|
|
}
|
|
|
|
public function qrcode(Request $request, string $provisioningToken)
|
|
{
|
|
$account = Account::withoutGlobalScopes()
|
|
->where('id', function ($query) use ($provisioningToken) {
|
|
$query->select('account_id')
|
|
->from('provisioning_tokens')
|
|
->where('used', false)
|
|
->where('token', $provisioningToken);
|
|
})
|
|
->firstOrFail();
|
|
|
|
if ($account->activationExpired()) {
|
|
abort(404);
|
|
}
|
|
|
|
$params = ['provisioning_token' => $provisioningToken];
|
|
|
|
if ($request->has('reset_password')) {
|
|
$params['reset_password'] = true;
|
|
}
|
|
|
|
$url = route('provisioning.provision', $params);
|
|
|
|
$result = Builder::create()
|
|
->writer(new PngWriter())
|
|
->data($url)
|
|
->encoding(new Encoding('UTF-8'))
|
|
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
|
|
->size(300)
|
|
->margin(10)
|
|
->build();
|
|
|
|
return response($result->getString())
|
|
->header('Content-Type', $result->getMimeType())
|
|
->header('X-Qrcode-URL', $url);
|
|
}
|
|
|
|
/**
|
|
* auth_token based provisioning
|
|
*/
|
|
public function authToken(Request $request, string $token)
|
|
{
|
|
$authToken = AuthToken::where('token', $token)->valid()->firstOrFail();
|
|
|
|
if ($authToken->account) {
|
|
$account = $authToken->account;
|
|
$authToken->delete();
|
|
|
|
return $this->generateProvisioning($request, $account);
|
|
}
|
|
|
|
abort(404);
|
|
}
|
|
|
|
/**
|
|
* Authenticated provisioning
|
|
*/
|
|
public function me(Request $request)
|
|
{
|
|
$this->checkProvisioningHeader($request);
|
|
|
|
return $this->generateProvisioning($request, $request->user());
|
|
}
|
|
|
|
/**
|
|
* Get the base provisioning, with authentication
|
|
*/
|
|
public function show(Request $request)
|
|
{
|
|
$this->checkProvisioningHeader($request);
|
|
|
|
return $this->generateProvisioning($request);
|
|
}
|
|
|
|
/**
|
|
* Provisioning Token based provisioning
|
|
*/
|
|
public function provision(Request $request, string $provisioningToken)
|
|
{
|
|
$this->checkProvisioningHeader($request);
|
|
|
|
$account = Account::withoutGlobalScopes()
|
|
->where('id', function ($query) use ($provisioningToken) {
|
|
$query->select('account_id')
|
|
->from('provisioning_tokens')
|
|
->where('used', false)
|
|
->where('token', $provisioningToken);
|
|
})
|
|
->firstOrFail();
|
|
|
|
if ($account->activationExpired() || ($provisioningToken != $account->provisioning_token)) {
|
|
return abort(404);
|
|
}
|
|
|
|
if ($account->currentProvisioningToken->expired()) {
|
|
return abort(410, 'Expired');
|
|
}
|
|
|
|
$account->activated = true;
|
|
$account->currentProvisioningToken->consume();
|
|
$account->save();
|
|
|
|
return $this->generateProvisioning($request, $account);
|
|
}
|
|
|
|
private function checkProvisioningHeader(Request $request)
|
|
{
|
|
if (!$request->hasHeader('x-linphone-provisioning')
|
|
&& config('app.provisioning_use_x_linphone_provisioning_header')) {
|
|
abort(400, 'x-linphone-provisioning header is missing');
|
|
}
|
|
}
|
|
|
|
private function generateProvisioning(Request $request, Account $account = null)
|
|
{
|
|
// Load the hooks if they exists
|
|
$provisioningHooks = config_path('provisioning_hooks.php');
|
|
|
|
if (file_exists($provisioningHooks)) {
|
|
require_once($provisioningHooks);
|
|
}
|
|
|
|
$dom = new \DOMDocument('1.0', 'UTF-8');
|
|
$xpath = new \DOMXpath($dom);
|
|
$config = $dom->createElement('config');
|
|
$config->setAttribute('xmlns', 'http://www.linphone.org/xsds/lpconfig.xsd');
|
|
|
|
$dom->appendChild($config);
|
|
|
|
// Default RC file handling
|
|
$rcFile = config('app.provisioning_rc_file');
|
|
|
|
if (file_exists($rcFile)) {
|
|
$rc = parse_ini_file($rcFile, true);
|
|
|
|
foreach ($rc as $sectionName => $values) {
|
|
$section = $dom->createElement('section');
|
|
$section->setAttribute('name', $sectionName);
|
|
|
|
foreach ($values as $key => $value) {
|
|
$entry = $dom->createElement('entry', $value);
|
|
$entry->setAttribute('name', $key);
|
|
$section->appendChild($entry);
|
|
}
|
|
|
|
$config->appendChild($section);
|
|
}
|
|
}
|
|
|
|
// Password reset
|
|
if ($account && $request->has('reset_password')) {
|
|
$account->updatePassword(Str::random(10));
|
|
}
|
|
|
|
$section = $dom->createElement('section');
|
|
$section->setAttribute('name', 'misc');
|
|
|
|
$entry = $dom->createElement('entry', route('account.contacts.vcard.index'));
|
|
$entry->setAttribute('name', 'contacts-vcard-list');
|
|
$section->appendChild($entry);
|
|
|
|
$config->appendChild($section);
|
|
|
|
if ($account) {
|
|
$ui = $xpath->query("//section[@name='ui']")->item(0);
|
|
|
|
if ($ui == null && $account->sipDomain) {
|
|
$section = $dom->createElement('section');
|
|
$section->setAttribute('name', 'ui');
|
|
|
|
foreach ([
|
|
'super',
|
|
'disable_chat_feature',
|
|
'disable_meetings_feature',
|
|
'disable_broadcast_feature',
|
|
'hide_settings',
|
|
'hide_account_settings',
|
|
'disable_call_recordings_feature',
|
|
'only_display_sip_uri_username',
|
|
'assistant_hide_create_account',
|
|
'assistant_disable_qr_code',
|
|
'assistant_hide_third_party_account',
|
|
'max_account',
|
|
] as $key) {
|
|
// Cast the booleans into integers
|
|
$entry = $dom->createElement('entry', (int)$account->sipDomain->$key);
|
|
$entry->setAttribute('name', $key);
|
|
$section->appendChild($entry);
|
|
}
|
|
|
|
$config->appendChild($section);
|
|
}
|
|
|
|
$section = $xpath->query("//section[@name='proxy_0']")->item(0);
|
|
|
|
if ($section == null) {
|
|
$section = $dom->createElement('section');
|
|
$section->setAttribute('name', 'proxy_0');
|
|
$config->appendChild($section);
|
|
}
|
|
|
|
$entry = $dom->createElement('entry', $account->fullIdentifier);
|
|
$entry->setAttribute('name', 'reg_identity');
|
|
$section->appendChild($entry);
|
|
|
|
// Complete the section with the Proxy hook
|
|
if (function_exists('provisioningProxyHook')) {
|
|
provisioningProxyHook($section, $request, $account);
|
|
}
|
|
|
|
$passwords = $account->passwords()->get();
|
|
$authInfoIndex = 0;
|
|
|
|
// CoTURN
|
|
if (hasCoTURNConfigured()) {
|
|
list($username, $password) = array_values(getCoTURNCredentials());
|
|
|
|
// 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);
|
|
}
|
|
|
|
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);
|
|
$entry->setAttribute('name', 'username');
|
|
$section->appendChild($entry);
|
|
|
|
$entry = $dom->createElement('entry', $account->domain);
|
|
$entry->setAttribute('name', 'domain');
|
|
$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);
|
|
}
|
|
|
|
$authInfoIndex++;
|
|
}
|
|
}
|
|
|
|
// Complete the section with the Auth hook
|
|
if (function_exists('provisioningAdditionalSectionHook')) {
|
|
provisioningAdditionalSectionHook($config, $request, $account);
|
|
}
|
|
|
|
// Overwrite all the entries
|
|
if (config('app.provisioning_overwrite_all')) {
|
|
$xpath = new \DOMXpath($dom);
|
|
$entries = $xpath->query("//section/entry");
|
|
if (!is_null($entries)) {
|
|
foreach ($entries as $entry) {
|
|
$entry->setAttribute('overwrite', 'true');
|
|
}
|
|
}
|
|
}
|
|
|
|
return response($dom->saveXML($dom->documentElement))->header('Content-Type', 'application/xml');
|
|
}
|
|
}
|