mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 01:58:07 +00:00
Add ExternalAccounts and related features
- Consume an ExternalAccount on Account creation - Add a tombstone to an ExternalAccount to ensure non re-usage - Add related tests - Generalize Utils - Stop public registration when there is no ExternalAccounts left - Add GenerateExternalAccounts, ExportToExternalAccounts and ImportExternalAccounts console scripts - Provision the ExternalAccount using the depends_on/idkey pair
This commit is contained in:
parent
093619a22c
commit
7a17897193
35 changed files with 694 additions and 227 deletions
|
|
@ -20,6 +20,9 @@ ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com # Proxy registrar address, can b
|
|||
ACCOUNT_TRANSPORT_PROTOCOL_TEXT="TLS (recommended), TCP or UDP" # Simple text, to explain how the SIP server can be reached
|
||||
ACCOUNT_REALM=null # Default realm for the accounts, fallback to the domain if not set, enforce null by default
|
||||
|
||||
# Account creation
|
||||
ACCOUNT_CONSUME_EXTERNAL_ACCOUNT_ON_CREATE=false
|
||||
|
||||
# Account provisioning
|
||||
ACCOUNT_PROVISIONING_RC_FILE=
|
||||
ACCOUNT_PROVISIONING_OVERWRITE_ALL=
|
||||
|
|
|
|||
|
|
@ -179,6 +179,26 @@ This command will set the admin role to any available Flexisip account (the exte
|
|||
|
||||
Once one account is declared as administrator, you can directly configure the other ones using the web panel.
|
||||
|
||||
### Generate External Accounts
|
||||
|
||||
Generate `amount` accounts defined by the `group` label.
|
||||
The generated accounts will have a random username suffixed by the group name.
|
||||
|
||||
accounts:generate-external {amount} {group}
|
||||
|
||||
### Export External Accounts
|
||||
|
||||
Export all the accounts defined by the `group` label.
|
||||
The command generates a JSON file containing the accounts ready to by imported as External Accounts in the current directory. A specific path can be defined using the `--o|output` optional parameter.
|
||||
|
||||
accounts:export-to-externals {group} {--o|output=}
|
||||
|
||||
### Import External Accounts
|
||||
|
||||
Import accounts previously exported as a JSON file. Accounts previously imported will be skipped in the process.
|
||||
|
||||
accounts:import-externals {file_path}
|
||||
|
||||
## Provisioning
|
||||
|
||||
FlexiAPI is providing endpoints to provision Liblinphone powered devices. You can find more documentation about it on the `/api#provisioning` documentation page.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ use Illuminate\Support\Str;
|
|||
use App\ApiKey;
|
||||
use App\Password;
|
||||
use App\EmailChanged;
|
||||
use App\Helpers\Utils;
|
||||
use App\Mail\ChangingEmail;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
|
@ -63,6 +62,21 @@ class Account extends Authenticatable
|
|||
$builder->where('domain', config('app.sip_domain'));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* External account handling
|
||||
*/
|
||||
static::creating(function ($account) {
|
||||
if (config('app.consume_external_account_on_create') && !getAvailableExternalAccount()) {
|
||||
abort(403, 'Accounts cannot be created on the server');
|
||||
}
|
||||
});
|
||||
|
||||
static::created(function ($account) {
|
||||
if (config('app.consume_external_account_on_create')) {
|
||||
$account->attachExternalAccount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeSip($query, string $sip)
|
||||
|
|
@ -109,6 +123,11 @@ class Account extends Authenticatable
|
|||
return $this->hasOne('App\ApiKey');
|
||||
}
|
||||
|
||||
public function externalAccount()
|
||||
{
|
||||
return $this->hasOne('App\ExternalAccount');
|
||||
}
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->belongsToMany('App\Account', 'contacts', 'account_id', 'contact_id');
|
||||
|
|
@ -203,6 +222,17 @@ class Account extends Authenticatable
|
|||
return ($this->activationExpiration && $this->activationExpiration->isExpired());
|
||||
}
|
||||
|
||||
public function attachExternalAccount(): bool
|
||||
{
|
||||
$externalAccount = getAvailableExternalAccount();
|
||||
|
||||
if (!$externalAccount) abort(403, 'No External Account left');
|
||||
|
||||
$externalAccount->account_id = $this->id;
|
||||
$externalAccount->used = true;
|
||||
return $externalAccount->save();
|
||||
}
|
||||
|
||||
public function requestEmailUpdate(string $newEmail)
|
||||
{
|
||||
// Remove all the old requests
|
||||
|
|
@ -272,7 +302,7 @@ class Account extends Authenticatable
|
|||
|
||||
$password = new Password;
|
||||
$password->account_id = $this->id;
|
||||
$password->password = Utils::bchash($this->username, $this->resolvedRealm, $newPassword, $algorithm);
|
||||
$password->password = bchash($this->username, $this->resolvedRealm, $newPassword, $algorithm);
|
||||
$password->algorithm = $algorithm;
|
||||
$password->save();
|
||||
}
|
||||
|
|
|
|||
51
flexiapi/app/Console/Commands/ExportToExternalAccounts.php
Normal file
51
flexiapi/app/Console/Commands/ExportToExternalAccounts.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExportToExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:export-to-externals {group} {--o|output=}';
|
||||
|
||||
protected $description = 'Export accounts from a group as external ones';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$accounts = Account::where('group', $this->argument('group'))
|
||||
->with('passwords')
|
||||
->get();
|
||||
|
||||
if ($accounts->count() == 0) {
|
||||
$this->error('Nothing to export');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Exporting '.$accounts->count().' accounts');
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
array_push($data, [
|
||||
'username' => $account->username,
|
||||
'domain' => $account->domain,
|
||||
'group' => $account->group,
|
||||
'password' => $account->passwords->first()->password,
|
||||
'algorithm' => $account->passwords->first()->algorithm,
|
||||
]);
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$this->option('output') ?? getcwd() . '/exported_accounts.json',
|
||||
json_encode($data)
|
||||
);
|
||||
|
||||
$this->info('Exported');
|
||||
}
|
||||
}
|
||||
65
flexiapi/app/Console/Commands/GenerateExternalAccounts.php
Normal file
65
flexiapi/app/Console/Commands/GenerateExternalAccounts.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Account;
|
||||
use App\Password;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:generate-external {amount} {group}';
|
||||
|
||||
protected $description = 'Generate external accounts in the designed group';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$accounts = collect();
|
||||
$passwords = collect();
|
||||
$algorithm = 'SHA-256';
|
||||
|
||||
$i = 0;
|
||||
|
||||
while ($i < $this->argument('amount')) {
|
||||
$account = new Account;
|
||||
$account->username = $this->argument('group') . '_' . Str::random(6);
|
||||
$account->domain = config('app.sip_domain');
|
||||
$account->activated = 1;
|
||||
$account->ip_address = '127.0.0.1';
|
||||
$account->user_agent = 'External Account Generator';
|
||||
$account->group = $this->argument('group');
|
||||
$account->creation_time = Carbon::now();
|
||||
$i++;
|
||||
|
||||
$account->push($account->toArray());
|
||||
}
|
||||
|
||||
Account::insert($accounts->toArray());
|
||||
|
||||
$insertedAccounts = Account::where('group', $this->argument('group'))
|
||||
->orderBy('creation_time', 'desc')
|
||||
->take($this->argument('amount'))
|
||||
->get();
|
||||
|
||||
foreach ($insertedAccounts as $account) {
|
||||
$password = new Password;
|
||||
$password->account_id = $account->id;
|
||||
$password->password = bchash($account->username, $account->resolvedRealm, Str::random(6), $algorithm);
|
||||
$password->algorithm = $algorithm;
|
||||
$passwords->push($password->only(['account_id', 'password', 'algorithm']));
|
||||
}
|
||||
|
||||
Password::insert($passwords->toArray());
|
||||
|
||||
$this->info($this->argument('amount') . ' accounts created under the "' . $this->argument('group') . '" group');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
67
flexiapi/app/Console/Commands/ImportExternalAccounts.php
Normal file
67
flexiapi/app/Console/Commands/ImportExternalAccounts.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:import-externals {file_path}';
|
||||
|
||||
protected $description = 'Import external accounts from a file';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!file_exists($this->argument('file_path'))) {
|
||||
$this->error('The file does not exists');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$json = json_decode(file_get_contents($this->argument('file_path')));
|
||||
|
||||
if (empty($json)) {
|
||||
$this->error('Nothing to import or incorrect file');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$existingUsernames = ExternalAccount::select('username')
|
||||
->from('external_accounts')
|
||||
->get()
|
||||
->pluck('username');
|
||||
$existingCounter = 0;
|
||||
$importedCounter = 0;
|
||||
|
||||
$externalAccounts = collect();
|
||||
foreach ($json as $account) {
|
||||
if ($existingUsernames->contains($account->username)) {
|
||||
$existingCounter++;
|
||||
} else {
|
||||
$externalAccount = new ExternalAccount;
|
||||
$externalAccount->username = $account->username;
|
||||
$externalAccount->domain = $account->domain;
|
||||
$externalAccount->group = $account->group;
|
||||
$externalAccount->password = $account->password;
|
||||
$externalAccount->algorithm = $account->algorithm;
|
||||
|
||||
$externalAccounts->push($externalAccount->toArray());
|
||||
$importedCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
ExternalAccount::insert($externalAccounts->toArray());
|
||||
|
||||
$this->info($importedCounter . ' accounts imported');
|
||||
|
||||
if ($existingCounter > 0) {
|
||||
$this->info($existingCounter . ' accounts already in the database');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
26
flexiapi/app/ExternalAccount.php
Normal file
26
flexiapi/app/ExternalAccount.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ExternalAccount extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
}
|
||||
|
||||
public function getIdentifierAttribute()
|
||||
{
|
||||
return $this->attributes['username'].'@'.$this->attributes['domain'];
|
||||
}
|
||||
|
||||
public function getResolvedRealmAttribute()
|
||||
{
|
||||
return config('app.realm') ?? $this->attributes['domain'];
|
||||
}
|
||||
}
|
||||
|
|
@ -17,77 +17,97 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Account;
|
||||
use App\DigestNonce;
|
||||
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
|
||||
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
|
||||
class Utils
|
||||
function generateNonce(): string
|
||||
{
|
||||
public static function generateNonce(): string
|
||||
{
|
||||
return Str::random(32);
|
||||
}
|
||||
|
||||
public static function generateValidNonce(Account $account): string
|
||||
{
|
||||
$nonce = new DigestNonce;
|
||||
$nonce->account_id = $account->id;
|
||||
$nonce->nonce = Utils::generateNonce();
|
||||
$nonce->save();
|
||||
|
||||
return $nonce->nonce;
|
||||
}
|
||||
|
||||
public static function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5')
|
||||
{
|
||||
$algos = ['MD5' => 'md5', 'SHA-256' => 'sha256'];
|
||||
|
||||
return hash($algos[$algorithm], $username.':'.$domain.':'.$password);
|
||||
}
|
||||
|
||||
public static function generatePin()
|
||||
{
|
||||
return mt_rand(1000, 9999);
|
||||
}
|
||||
|
||||
public static function percent($value, $max)
|
||||
{
|
||||
if ($max == 0) $max = 1;
|
||||
return round(($value*100)/$max, 2);
|
||||
}
|
||||
|
||||
public static function markdownDocumentationView($view)
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->addExtension(new HeadingPermalinkExtension);
|
||||
$environment->addExtension(new TableOfContentsExtension);
|
||||
$environment->mergeConfig([
|
||||
'heading_permalink' => [
|
||||
'html_class' => 'permalink',
|
||||
'insert' => 'after',
|
||||
'title' => 'Permalink',
|
||||
'id_prefix' => '',
|
||||
'fragment_prefix' => '',
|
||||
],
|
||||
'table_of_contents' => [
|
||||
'html_class' => 'table-of-contents float-right',
|
||||
],
|
||||
]);
|
||||
|
||||
$converter = new MarkdownConverter($environment);
|
||||
|
||||
return (string) $converter->convertToHtml(
|
||||
view($view, [
|
||||
'app_name' => config('app.name')
|
||||
])->render()
|
||||
);
|
||||
}
|
||||
return Str::random(32);
|
||||
}
|
||||
|
||||
function generateValidNonce(Account $account): string
|
||||
{
|
||||
$nonce = new DigestNonce;
|
||||
$nonce->account_id = $account->id;
|
||||
$nonce->nonce = generateNonce();
|
||||
$nonce->save();
|
||||
|
||||
return $nonce->nonce;
|
||||
}
|
||||
|
||||
function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5')
|
||||
{
|
||||
$algos = ['MD5' => 'md5', 'SHA-256' => 'sha256'];
|
||||
|
||||
return hash($algos[$algorithm], $username.':'.$domain.':'.$password);
|
||||
}
|
||||
|
||||
function generatePin()
|
||||
{
|
||||
return mt_rand(1000, 9999);
|
||||
}
|
||||
|
||||
function percent($value, $max)
|
||||
{
|
||||
if ($max == 0) $max = 1;
|
||||
return round(($value*100)/$max, 2);
|
||||
}
|
||||
|
||||
function markdownDocumentationView($view): string
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->addExtension(new HeadingPermalinkExtension);
|
||||
$environment->addExtension(new TableOfContentsExtension);
|
||||
$environment->mergeConfig([
|
||||
'heading_permalink' => [
|
||||
'html_class' => 'permalink',
|
||||
'insert' => 'after',
|
||||
'title' => 'Permalink',
|
||||
'id_prefix' => '',
|
||||
'fragment_prefix' => '',
|
||||
],
|
||||
'table_of_contents' => [
|
||||
'html_class' => 'table-of-contents float-right',
|
||||
],
|
||||
]);
|
||||
|
||||
$converter = new MarkdownConverter($environment);
|
||||
|
||||
return (string) $converter->convertToHtml(
|
||||
view($view, [
|
||||
'app_name' => config('app.name')
|
||||
])->render()
|
||||
);
|
||||
}
|
||||
|
||||
function getAvailableExternalAccount(): ?ExternalAccount
|
||||
{
|
||||
if (Schema::hasTable('external_accounts')) {
|
||||
return ExternalAccount::where('used', false)
|
||||
->where('account_id', null)
|
||||
->first();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function publicRegistrationEnabled(): bool
|
||||
{
|
||||
if (config('app.public_registration')) {
|
||||
if (config('app.consume_external_account_on_create')) {
|
||||
return (bool)getAvailableExternalAccount();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ use Illuminate\Support\Facades\Auth;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Account;
|
||||
use App\AccountTombstone;
|
||||
use App\Helpers\Utils;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
|
@ -43,7 +42,7 @@ class AccountController extends Controller
|
|||
public function documentation(Request $request)
|
||||
{
|
||||
return view('account.documentation', [
|
||||
'documentation' => Utils::markdownDocumentationView('account.documentation_markdown')
|
||||
'documentation' => markdownDocumentationView('account.documentation_markdown')
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ use Illuminate\Support\Facades\Mail;
|
|||
use App\Account;
|
||||
use App\Alias;
|
||||
use App\AuthToken;
|
||||
use App\Helpers\Utils;
|
||||
use App\Libraries\OvhSMS;
|
||||
use App\Mail\PasswordAuthentication;
|
||||
|
||||
|
|
@ -72,7 +71,7 @@ class AuthenticateController extends Controller
|
|||
foreach ($account->passwords as $password) {
|
||||
if (hash_equals(
|
||||
$password->password,
|
||||
Utils::bchash($account->username, $account->resolvedRealm, $request->get('password'), $password->algorithm)
|
||||
bchash($account->username, $account->resolvedRealm, $request->get('password'), $password->algorithm)
|
||||
)) {
|
||||
Auth::login($account);
|
||||
return redirect()->route('account.panel');
|
||||
|
|
@ -208,7 +207,7 @@ class AuthenticateController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
$account->confirmation_key = Utils::generatePin();
|
||||
$account->confirmation_key = generatePin();
|
||||
$account->save();
|
||||
|
||||
$ovhSMS = new OvhSMS;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Helpers\Utils;
|
||||
use App\Mail\ConfirmedRegistration;
|
||||
|
||||
class PasswordController extends Controller
|
||||
|
|
|
|||
|
|
@ -143,10 +143,12 @@ class ProvisioningController extends Controller
|
|||
$config->appendChild($section);
|
||||
|
||||
if ($account && !$account->activationExpired()) {
|
||||
$externalAccount = $account->externalAccount;
|
||||
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'proxy_' . $proxyConfigIndex);
|
||||
|
||||
$entry = $dom->createElement('entry', '<sip:'.$account->identifier.'>');
|
||||
$entry = $dom->createElement('entry', '<sip:' . $account->identifier . '>');
|
||||
$entry->setAttribute('name', 'reg_identity');
|
||||
$section->appendChild($entry);
|
||||
|
||||
|
|
@ -163,6 +165,12 @@ class ProvisioningController extends Controller
|
|||
provisioningProxyHook($section, $request, $account);
|
||||
}
|
||||
|
||||
if ($externalAccount) {
|
||||
$entry = $dom->createElement('entry', 'external_account');
|
||||
$entry->setAttribute('name', 'depends_on');
|
||||
$section->appendChild($entry);
|
||||
}
|
||||
|
||||
$config->appendChild($section);
|
||||
|
||||
$passwords = $account->passwords()->get();
|
||||
|
|
@ -199,19 +207,71 @@ class ProvisioningController extends Controller
|
|||
$config->appendChild($section);
|
||||
|
||||
$authInfoIndex++;
|
||||
|
||||
}
|
||||
|
||||
if ($provisioningToken) {
|
||||
// Activate the account
|
||||
if ($account->activated == false
|
||||
&& $provisioningToken == $account->provisioning_token) {
|
||||
if (
|
||||
$account->activated == false
|
||||
&& $provisioningToken == $account->provisioning_token
|
||||
) {
|
||||
$account->activated = true;
|
||||
}
|
||||
|
||||
$account->provisioning_token = null;
|
||||
$account->save();
|
||||
}
|
||||
|
||||
$proxyConfigIndex++;
|
||||
|
||||
// External Account handling
|
||||
if ($externalAccount) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'proxy_' . $proxyConfigIndex);
|
||||
|
||||
$entry = $dom->createElement('entry', '<sip:' . $externalAccount->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);
|
||||
|
||||
$entry = $dom->createElement('entry', 'external_account');
|
||||
$entry->setAttribute('name', 'idkey');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$config->appendChild($section);
|
||||
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'auth_info_' . $authInfoIndex);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->username);
|
||||
$entry->setAttribute('name', 'username');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->domain);
|
||||
$entry->setAttribute('name', 'domain');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->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', $externalAccount->algorithm);
|
||||
$entry->setAttribute('name', 'algorithm');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$config->appendChild($section);
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the section with the Auth hook
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ use App\Alias;
|
|||
use App\Rules\WithoutSpaces;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
use App\Helpers\Utils;
|
||||
use App\Libraries\OvhSMS;
|
||||
use App\Mail\RegisterConfirmation;
|
||||
use App\Mail\NewsletterRegistration;
|
||||
|
|
@ -155,7 +154,7 @@ class RegisterController extends Controller
|
|||
$alias->account_id = $account->id;
|
||||
$alias->save();
|
||||
|
||||
$account->confirmation_key = Utils::generatePin();
|
||||
$account->confirmation_key = generatePin();
|
||||
$account->save();
|
||||
|
||||
$ovhSMS = new OvhSMS;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Carbon\Carbon;
|
|||
use App\Account;
|
||||
use App\Admin;
|
||||
use App\Alias;
|
||||
use App\ExternalAccount;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
|
|
@ -36,7 +37,7 @@ class AccountController extends Controller
|
|||
{
|
||||
public function index(Request $request, $search = '')
|
||||
{
|
||||
$accounts = Account::orderBy('creation_time', 'desc');
|
||||
$accounts = Account::orderBy('creation_time', 'desc')->with('externalAccount');
|
||||
|
||||
if (!empty($search)) {
|
||||
$accounts = $accounts->where('username', 'like', '%'.$search.'%');
|
||||
|
|
@ -51,6 +52,7 @@ class AccountController extends Controller
|
|||
public function show(int $id)
|
||||
{
|
||||
return view('admin.account.show', [
|
||||
'external_accounts_count' => ExternalAccount::where('used', false)->count(),
|
||||
'account' => Account::findOrFail($id)
|
||||
]);
|
||||
}
|
||||
|
|
@ -116,6 +118,16 @@ class AccountController extends Controller
|
|||
return redirect()->route('admin.account.index', $request->get('search'));
|
||||
}
|
||||
|
||||
public function attachExternalAccount(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->attachExternalAccount();
|
||||
|
||||
Log::channel('events')->info('Web Admin: ExternalAccount attached', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function activate(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ use Illuminate\Support\Facades\Log;
|
|||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Rules\WithoutSpaces;
|
||||
use App\Helpers\Utils;
|
||||
use App\Libraries\OvhSMS;
|
||||
|
||||
use App\PhoneChangeCode;
|
||||
|
|
@ -47,7 +46,7 @@ class AccountPhoneController extends Controller
|
|||
$phoneChangeCode = $account->phoneChangeCode ?? new PhoneChangeCode;
|
||||
$phoneChangeCode->account_id = $account->id;
|
||||
$phoneChangeCode->phone = $request->get('phone');
|
||||
$phoneChangeCode->code = Utils::generatePin();
|
||||
$phoneChangeCode->code = generatePin();
|
||||
$phoneChangeCode->save();
|
||||
|
||||
Log::channel('events')->info('API: Account phone change requested by SMS', ['id' => $account->identifier]);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Utils;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
|
@ -28,7 +27,7 @@ class ApiController extends Controller
|
|||
public function documentation(Request $request)
|
||||
{
|
||||
return view('api.documentation', [
|
||||
'documentation' => Utils::markdownDocumentationView('api.documentation_markdown')
|
||||
'documentation' => markdownDocumentationView('api.documentation_markdown')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Helpers\Utils;
|
||||
use App\Mail\ConfirmedRegistration;
|
||||
|
||||
class PasswordController extends Controller
|
||||
|
|
@ -48,7 +47,7 @@ class PasswordController extends Controller
|
|||
foreach ($account->passwords as $password) {
|
||||
if (hash_equals(
|
||||
$password->password,
|
||||
Utils::bchash($account->username, $account->resolvedRealm, $request->get('old_password'), $password->algorithm)
|
||||
bchash($account->username, $account->resolvedRealm, $request->get('old_password'), $password->algorithm)
|
||||
)) {
|
||||
$account->updatePassword($request->get('password'), $algorithm);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Account;
|
||||
use App\Helpers\Utils;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -170,7 +169,7 @@ class AuthenticateDigestOrKey
|
|||
{
|
||||
$response = new Response;
|
||||
|
||||
$nonce = Utils::generateValidNonce($account);
|
||||
$nonce = generateValidNonce($account);
|
||||
$headers = $this->generateAuthHeaders($account, $nonce);
|
||||
|
||||
if (!empty($headers)) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class Password extends Model
|
|||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['account_id', 'password', 'algorithm'];
|
||||
protected $hidden = ['id', 'password', 'account_id', 'created_at', 'updated_at'];
|
||||
|
||||
public function account()
|
||||
|
|
|
|||
18
flexiapi/app/Providers/HelperServiceProvider.php
Normal file
18
flexiapi/app/Providers/HelperServiceProvider.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class HelperServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
require_once app_path('Helpers/Utils.php');
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
185
flexiapi/composer.lock
generated
185
flexiapi/composer.lock
generated
|
|
@ -1468,16 +1468,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v8.83.17",
|
||||
"version": "v8.83.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "2cf142cd5100b02da248acad3988bdaba5635e16"
|
||||
"reference": "db8188e9cc8359a5c6706fa9d9f55aad7f235077"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/2cf142cd5100b02da248acad3988bdaba5635e16",
|
||||
"reference": "2cf142cd5100b02da248acad3988bdaba5635e16",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/db8188e9cc8359a5c6706fa9d9f55aad7f235077",
|
||||
"reference": "db8188e9cc8359a5c6706fa9d9f55aad7f235077",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1637,7 +1637,7 @@
|
|||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2022-06-21T14:38:31+00:00"
|
||||
"time": "2022-06-28T14:30:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
|
|
@ -2187,16 +2187,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "2.58.0",
|
||||
"version": "2.59.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055"
|
||||
"reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055",
|
||||
"reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
|
||||
"reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2211,11 +2211,12 @@
|
|||
"doctrine/orm": "^2.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"kylekatarnls/multi-tester": "^2.0",
|
||||
"ondrejmirtes/better-reflection": "*",
|
||||
"phpmd/phpmd": "^2.9",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12.54 || ^1.0",
|
||||
"phpunit/php-file-iterator": "^2.0.5",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.23",
|
||||
"phpstan/phpstan": "^0.12.99 || ^1.7.14",
|
||||
"phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"bin": [
|
||||
|
|
@ -2272,15 +2273,19 @@
|
|||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/Carbon",
|
||||
"type": "open_collective"
|
||||
"url": "https://github.com/sponsors/kylekatarnls",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
|
||||
"url": "https://opencollective.com/Carbon#sponsor",
|
||||
"type": "opencollective"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-25T19:31:17+00:00"
|
||||
"time": "2022-06-29T21:43:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
|
|
@ -2878,16 +2883,16 @@
|
|||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
"version": "v0.11.5",
|
||||
"version": "v0.11.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bobthecow/psysh.git",
|
||||
"reference": "c23686f9c48ca202710dbb967df8385a952a2daf"
|
||||
"reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf",
|
||||
"reference": "c23686f9c48ca202710dbb967df8385a952a2daf",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/77fc7270031fbc28f9a7bea31385da5c4855cb7a",
|
||||
"reference": "77fc7270031fbc28f9a7bea31385da5c4855cb7a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2948,9 +2953,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bobthecow/psysh/issues",
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.5"
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.11.7"
|
||||
},
|
||||
"time": "2022-05-27T18:03:49+00:00"
|
||||
"time": "2022-07-07T13:49:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
|
|
@ -4099,7 +4104,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v2.5.1",
|
||||
"version": "v2.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
|
|
@ -4146,7 +4151,7 @@
|
|||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4322,7 +4327,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
"version": "v2.5.1",
|
||||
"version": "v2.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
|
||||
|
|
@ -4381,7 +4386,7 @@
|
|||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.1"
|
||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5701,16 +5706,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v2.5.1",
|
||||
"version": "v2.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c"
|
||||
"reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
|
||||
"reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
|
||||
"reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5764,7 +5769,7 @@
|
|||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v2.5.1"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v2.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5780,7 +5785,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-13T20:07:29+00:00"
|
||||
"time": "2022-05-30T19:17:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
|
|
@ -5967,16 +5972,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
"version": "v2.5.1",
|
||||
"version": "v2.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation-contracts.git",
|
||||
"reference": "1211df0afa701e45a04253110e959d4af4ef0f07"
|
||||
"reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1211df0afa701e45a04253110e959d4af4ef0f07",
|
||||
"reference": "1211df0afa701e45a04253110e959d4af4ef0f07",
|
||||
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe",
|
||||
"reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6025,7 +6030,7 @@
|
|||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation-contracts/tree/v2.5.1"
|
||||
"source": "https://github.com/symfony/translation-contracts/tree/v2.5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -6041,7 +6046,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-01-02T09:53:40+00:00"
|
||||
"time": "2022-06-27T16:58:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
|
|
@ -6401,30 +6406,29 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "barryvdh/laravel-debugbar",
|
||||
"version": "v3.6.7",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||
"reference": "b96f9820aaf1ff9afe945207883149e1c7afb298"
|
||||
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/b96f9820aaf1ff9afe945207883149e1c7afb298",
|
||||
"reference": "b96f9820aaf1ff9afe945207883149e1c7afb298",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271",
|
||||
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/routing": "^6|^7|^8|^9",
|
||||
"illuminate/session": "^6|^7|^8|^9",
|
||||
"illuminate/support": "^6|^7|^8|^9",
|
||||
"illuminate/routing": "^7|^8|^9",
|
||||
"illuminate/session": "^7|^8|^9",
|
||||
"illuminate/support": "^7|^8|^9",
|
||||
"maximebf/debugbar": "^1.17.2",
|
||||
"php": ">=7.2",
|
||||
"symfony/debug": "^4.3|^5|^6",
|
||||
"symfony/finder": "^4.3|^5|^6"
|
||||
"php": ">=7.2.5",
|
||||
"symfony/finder": "^5|^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"orchestra/testbench-dusk": "^4|^5|^6|^7",
|
||||
"orchestra/testbench-dusk": "^5|^6|^7",
|
||||
"phpunit/phpunit": "^8.5|^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
|
|
@ -6470,7 +6474,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.7"
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -6482,7 +6486,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-09T07:52:32+00:00"
|
||||
"time": "2022-07-11T09:26:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
|
|
@ -6621,16 +6625,16 @@
|
|||
},
|
||||
{
|
||||
"name": "facade/ignition",
|
||||
"version": "2.17.5",
|
||||
"version": "2.17.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facade/ignition.git",
|
||||
"reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c"
|
||||
"reference": "6acd82e986a2ecee89e2e68adfc30a1936d1ab7c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/facade/ignition/zipball/1d71996f83c9a5a7807331b8986ac890352b7a0c",
|
||||
"reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c",
|
||||
"url": "https://api.github.com/repos/facade/ignition/zipball/6acd82e986a2ecee89e2e68adfc30a1936d1ab7c",
|
||||
"reference": "6acd82e986a2ecee89e2e68adfc30a1936d1ab7c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6695,7 +6699,7 @@
|
|||
"issues": "https://github.com/facade/ignition/issues",
|
||||
"source": "https://github.com/facade/ignition"
|
||||
},
|
||||
"time": "2022-02-23T18:31:24+00:00"
|
||||
"time": "2022-06-30T18:26:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "facade/ignition-contracts",
|
||||
|
|
@ -8933,75 +8937,6 @@
|
|||
],
|
||||
"time": "2020-09-28T06:39:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v4.4.41",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "6637e62480b60817b9a6984154a533e8e64c6bd5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/6637e62480b60817b9a6984154a533e8e64c6bd5",
|
||||
"reference": "6637e62480b60817b9a6984154a533e8e64c6bd5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": "<3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "^3.4|^4.0|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Debug\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides tools to ease debugging PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/debug/tree/v4.4.41"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"abandoned": "symfony/error-handler",
|
||||
"time": "2022-04-12T15:19:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.1",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ return [
|
|||
'proxy_registrar_address' => env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'),
|
||||
'transport_protocol_text' => env('ACCOUNT_TRANSPORT_PROTOCOL_TEXT', 'TLS (recommended), TCP or UDP'),
|
||||
|
||||
'consume_external_account_on_create' => env('ACCOUNT_CONSUME_EXTERNAL_ACCOUNT_ON_CREATE', false),
|
||||
|
||||
/**
|
||||
* Time limit before the API Key and related cookie are expired
|
||||
*/
|
||||
|
|
@ -224,6 +226,7 @@ return [
|
|||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\HelperServiceProvider::class,
|
||||
|
||||
],
|
||||
|
||||
|
|
@ -273,7 +276,7 @@ return [
|
|||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||
'Str' => Illuminate\Support\Str::class,
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Utils' => App\Helpers\Utils::class,
|
||||
//'Utils' => App\Helpers\class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
],
|
||||
|
|
|
|||
25
flexiapi/database/factories/ExternalAccountFactory.php
Normal file
25
flexiapi/database/factories/ExternalAccountFactory.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ExternalAccountFactory extends Factory
|
||||
{
|
||||
protected $model = ExternalAccount::class;
|
||||
|
||||
public function definition()
|
||||
{
|
||||
$username = $this->faker->username;
|
||||
$realm = config('app.realm') ?? config('app.sip_domain');
|
||||
|
||||
return [
|
||||
'username' => $username,
|
||||
'domain' => config('app.sip_domain'),
|
||||
'group' => 'test',
|
||||
'password' => hash('sha256', $username.':'.$realm.':testtest'),
|
||||
'algorithm' => 'SHA-256',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Helpers\Utils;
|
||||
use App\Password;
|
||||
use App\PhoneChangeCode;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
|
@ -35,7 +34,7 @@ class PhoneChangeCodeFactory extends Factory
|
|||
|
||||
return [
|
||||
'account_id' => $password->account->id,
|
||||
'code' => Utils::generatePin(),
|
||||
'code' => generatePin(),
|
||||
'phone' => '+3312341234',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateExternalAccountsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('external_accounts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('username', 64);
|
||||
$table->string('domain', 64);
|
||||
$table->string('group', 16);
|
||||
$table->string('password', 255);
|
||||
$table->string('algorithm', 10)->default('MD5');
|
||||
$table->boolean('used')->default(false);
|
||||
|
||||
$table->integer('account_id')->unsigned()->nullable();
|
||||
$table->foreign('account_id')->references('id')
|
||||
->on('accounts')->onDelete('set null');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->string('group')->nullable();
|
||||
$table->index('group');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->dropIndex('accounts_group_index');
|
||||
$table->dropColumn('group');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('external_accounts');
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<hr />
|
||||
|
||||
<div class="list-group mb-3">
|
||||
@if (config('app.public_registration'))
|
||||
@if (publicRegistrationEnabled())
|
||||
<a href="{{ route('account.register') }}" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">Create an account</h5>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
@include('parts.password_recovery')
|
||||
@endif
|
||||
|
||||
@if (config('app.public_registration'))
|
||||
@if (publicRegistrationEnabled())
|
||||
<hr />
|
||||
|
||||
<p class="text-center">
|
||||
|
|
|
|||
|
|
@ -42,14 +42,17 @@
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@if ($account->externalAccount)
|
||||
<span class="badge badge-secondary" title="External Account attached">EA</span>
|
||||
@endif
|
||||
@if ($account->email)
|
||||
<span class="badge badge-info">Email</span>
|
||||
@endif
|
||||
@if ($account->activated)
|
||||
<span class="badge badge-success">Activated</span>
|
||||
<span class="badge badge-success" title="Activated">Act.</span>
|
||||
@endif
|
||||
@if ($account->admin)
|
||||
<span class="badge badge-primary">Admin</span>
|
||||
<span class="badge badge-primary" title="Admin">Adm.</span>
|
||||
@endif
|
||||
@if ($account->sha256Password)
|
||||
<span class="badge badge-info">SHA256</span>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<b>Email:</b> <a href="mailto:{{ $account->email }}">{{ $account->email }}</a><br />
|
||||
<b>DTMF Protocol:</b> @if ($account->dtmf_protocol) {{ $account->resolvedDtmfProtocol }}@endif<br />
|
||||
@if ($account->alias)<b>Phone number:</b> {{ $account->phone }}<br />@endif
|
||||
@if ($account->group)<b>Group:</b> {{ $account->group }}<br />@endif
|
||||
@if ($account->display_name)<b>Display name:</b> {{ $account->display_name }}<br />@endif
|
||||
</p>
|
||||
|
||||
|
|
@ -45,6 +46,16 @@
|
|||
<span class="badge badge-danger">Not Admin</span> <a href="{{ route('admin.account.admin', $account->id) }}">Add admin role</a>
|
||||
@endif
|
||||
|
||||
<h3 class="mt-3">External Account</h3>
|
||||
|
||||
@if ($account->externalAccount)
|
||||
<p>
|
||||
<b>Identifier:</b> {{ $account->externalAccount->identifier }}<br />
|
||||
</p>
|
||||
@else
|
||||
<a class="btn btn-sm @if ($external_accounts_count == 0)disabled @endif" href="{{ route('admin.account.external_account.attach', $account->id) }}">Attach an External Account ({{ $external_accounts_count}} left)</a>
|
||||
@endif
|
||||
|
||||
<h3 class="mt-3">Contacts</h3>
|
||||
|
||||
<table class="table">
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<div class="bar first" style="flex-basis: {{ Utils::percent($slice['phone'] - $slice['activated_phone'], $max) }}%"
|
||||
<div class="bar first" style="flex-basis: {{ percent($slice['phone'] - $slice['activated_phone'], $max) }}%"
|
||||
data-value="{{ $slice['phone'] - $slice['activated_phone'] }}"
|
||||
title="Unactivated phone: {{ $slice['phone'] - $slice['activated_phone'] }}"></div>
|
||||
<div class="bar first activated" style="flex-basis: {{ Utils::percent($slice['activated_phone'], $max) }}%"
|
||||
<div class="bar first activated" style="flex-basis: {{ percent($slice['activated_phone'], $max) }}%"
|
||||
data-value="{{ $slice['activated_phone'] }}"
|
||||
title="Activated phone: {{ $slice['activated_phone'] }}"></div>
|
||||
<div class="bar second" style="flex-basis: {{ Utils::percent($slice['email'] - $slice['activated_email'], $max) }}%"
|
||||
<div class="bar second" style="flex-basis: {{ percent($slice['email'] - $slice['activated_email'], $max) }}%"
|
||||
data-value="{{ $slice['email'] - $slice['activated_email'] }}"
|
||||
title="Unactivated email: {{ $slice['email'] - $slice['activated_email'] }}"></div>
|
||||
<div class="bar second activated" style="flex-basis: {{ Utils::percent($slice['activated_email'], $max) }}%"
|
||||
<div class="bar second activated" style="flex-basis: {{ percent($slice['activated_email'], $max) }}%"
|
||||
data-value="{{ $slice['activated_email'] }}"
|
||||
title="Activated email: {{ $slice['activated_email'] }}"></div>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
</ul>
|
||||
|
||||
<ul class="navbar-nav">
|
||||
@if (config('app.public_registration'))
|
||||
@if (publicRegistrationEnabled())
|
||||
<li class="nav-item @if (request()->routeIs('account.register')) active @endif">
|
||||
<a class="nav-link" href="{{ route('account.register') }}">Register</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ Route::get('provisioning/auth_token/{auth_token}', 'Account\ProvisioningControll
|
|||
Route::get('provisioning/qrcode/{provisioning_token}', 'Account\ProvisioningController@qrcode')->name('provisioning.qrcode');
|
||||
Route::get('provisioning/{provisioning_token?}', 'Account\ProvisioningController@show')->name('provisioning.show');
|
||||
|
||||
if (config('app.public_registration')) {
|
||||
if (publicRegistrationEnabled()) {
|
||||
if (config('app.phone_authentication')) {
|
||||
Route::get('register/phone', 'Account\RegisterController@registerPhone')->name('account.register.phone');
|
||||
Route::post('register/phone', 'Account\RegisterController@storePhone')->name('account.store.phone');
|
||||
|
|
@ -118,6 +118,8 @@ if (config('app.web_panel')) {
|
|||
Route::get('admin/accounts/{account}/activate', 'Admin\AccountController@activate')->name('admin.account.activate');
|
||||
Route::get('admin/accounts/{account}/deactivate', 'Admin\AccountController@deactivate')->name('admin.account.deactivate');
|
||||
|
||||
Route::get('admin/accounts/{account}/external_account/attach', 'Admin\AccountController@attachExternalAccount')->name('admin.account.external_account.attach');
|
||||
|
||||
Route::get('admin/accounts/{account}/admin', 'Admin\AccountController@admin')->name('admin.account.admin');
|
||||
Route::get('admin/accounts/{id}/unadmin', 'Admin\AccountController@unadmin')->name('admin.account.unadmin');
|
||||
|
||||
|
|
|
|||
83
flexiapi/tests/Feature/ExternalAccountTest.php
Normal file
83
flexiapi/tests/Feature/ExternalAccountTest.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?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 Tests\Feature;
|
||||
|
||||
use App\Admin;
|
||||
use App\Account;
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExternalAccountTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected $route = '/api/accounts';
|
||||
protected $provisioningRoute = '/provisioning/me';
|
||||
protected $method = 'POST';
|
||||
|
||||
public function testExternalAccountAttachOnCreate()
|
||||
{
|
||||
$admin = Admin::factory()->create();
|
||||
$password = $admin->account->passwords()->first();
|
||||
$password->account->generateApiKey();
|
||||
$password->account->save();
|
||||
|
||||
config()->set('app.consume_external_account_on_create', true);
|
||||
|
||||
// Seed an ExternalAccount
|
||||
$externalAccount = ExternalAccount::factory()->create();
|
||||
$externalAccount->save();
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => 'test',
|
||||
'domain' => 'example.com',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
'activated' => true,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// No ExternalAccount left
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => 'test2',
|
||||
'domain' => 'example.com',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
|
||||
$createdAccount = Account::where('username', 'test')->first();
|
||||
$createdAccount->generateApiKey();
|
||||
$createdAccount->save();
|
||||
|
||||
$response = $this->keyAuthenticated($createdAccount)
|
||||
->get($this->provisioningRoute)
|
||||
->assertStatus(200)
|
||||
->assertHeader('Content-Type', 'application/xml')
|
||||
->assertSee('ha1')
|
||||
->assertSee('idkey')
|
||||
->assertSee('depends_on');
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ namespace Tests;
|
|||
|
||||
use App\Password;
|
||||
use App\Account;
|
||||
use App\Helpers\Utils;
|
||||
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
|
||||
|
|
@ -59,7 +58,7 @@ abstract class TestCase extends BaseTestCase
|
|||
$challenge = \substr($response->headers->get('www-authenticate'), 7);
|
||||
$extractedChallenge = $this->extractAuthenticateHeader($challenge);
|
||||
|
||||
$cnonce = Utils::generateNonce();
|
||||
$cnonce = generateNonce();
|
||||
|
||||
$A1 = $password->password;
|
||||
$A2 = hash($hash, $this->method . ':' . $this->route);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#%define _datadir %{_datarootdir}
|
||||
#%define _docdir %{_datadir}/doc
|
||||
|
||||
%define build_number 142
|
||||
%define build_number 143
|
||||
%define var_dir /var/opt/belledonne-communications
|
||||
%define opt_dir /opt/belledonne-communications/share/flexisip-account-manager
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue