From 7a178971933685cd84f61bbe93d0b52a1d960e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Wed, 29 Jun 2022 16:19:37 +0200 Subject: [PATCH] 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 --- flexiapi/.env.example | 3 + flexiapi/README.md | 20 ++ flexiapi/app/Account.php | 34 +++- .../Commands/ExportToExternalAccounts.php | 51 +++++ .../Commands/GenerateExternalAccounts.php | 65 ++++++ .../Commands/ImportExternalAccounts.php | 67 +++++++ flexiapi/app/ExternalAccount.php | 26 +++ flexiapi/app/Helpers/Utils.php | 146 ++++++++------ .../Controllers/Account/AccountController.php | 3 +- .../Account/AuthenticateController.php | 5 +- .../Account/PasswordController.php | 1 - .../Account/ProvisioningController.php | 68 ++++++- .../Account/RegisterController.php | 3 +- .../Controllers/Admin/AccountController.php | 14 +- .../Api/AccountPhoneController.php | 3 +- .../Http/Controllers/Api/ApiController.php | 3 +- .../Controllers/Api/PasswordController.php | 3 +- .../Middleware/AuthenticateDigestOrKey.php | 3 +- flexiapi/app/Password.php | 1 + .../app/Providers/HelperServiceProvider.php | 18 ++ flexiapi/composer.lock | 185 ++++++------------ flexiapi/config/app.php | 5 +- .../factories/ExternalAccountFactory.php | 25 +++ .../factories/PhoneChangeCodeFactory.php | 3 +- ..._100221_create_external_accounts_table.php | 42 ++++ .../resources/views/account/home.blade.php | 2 +- .../resources/views/account/login.blade.php | 2 +- .../views/admin/account/index.blade.php | 7 +- .../views/admin/account/show.blade.php | 11 ++ .../admin/statistics/parts/columns.blade.php | 8 +- .../resources/views/layouts/main.blade.php | 2 +- flexiapi/routes/web.php | 4 +- .../tests/Feature/ExternalAccountTest.php | 83 ++++++++ flexiapi/tests/TestCase.php | 3 +- flexisip-account-manager.spec | 2 +- 35 files changed, 694 insertions(+), 227 deletions(-) create mode 100644 flexiapi/app/Console/Commands/ExportToExternalAccounts.php create mode 100644 flexiapi/app/Console/Commands/GenerateExternalAccounts.php create mode 100644 flexiapi/app/Console/Commands/ImportExternalAccounts.php create mode 100644 flexiapi/app/ExternalAccount.php create mode 100644 flexiapi/app/Providers/HelperServiceProvider.php create mode 100644 flexiapi/database/factories/ExternalAccountFactory.php create mode 100644 flexiapi/database/migrations/2022_06_29_100221_create_external_accounts_table.php create mode 100644 flexiapi/tests/Feature/ExternalAccountTest.php diff --git a/flexiapi/.env.example b/flexiapi/.env.example index 8a16f10..f0ec0a4 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -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= diff --git a/flexiapi/README.md b/flexiapi/README.md index aa99cb5..e953627 100644 --- a/flexiapi/README.md +++ b/flexiapi/README.md @@ -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. diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 844491b..1e6004a 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -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(); } diff --git a/flexiapi/app/Console/Commands/ExportToExternalAccounts.php b/flexiapi/app/Console/Commands/ExportToExternalAccounts.php new file mode 100644 index 0000000..02467cc --- /dev/null +++ b/flexiapi/app/Console/Commands/ExportToExternalAccounts.php @@ -0,0 +1,51 @@ +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'); + } +} diff --git a/flexiapi/app/Console/Commands/GenerateExternalAccounts.php b/flexiapi/app/Console/Commands/GenerateExternalAccounts.php new file mode 100644 index 0000000..01509dc --- /dev/null +++ b/flexiapi/app/Console/Commands/GenerateExternalAccounts.php @@ -0,0 +1,65 @@ +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; + } +} diff --git a/flexiapi/app/Console/Commands/ImportExternalAccounts.php b/flexiapi/app/Console/Commands/ImportExternalAccounts.php new file mode 100644 index 0000000..51ee7e0 --- /dev/null +++ b/flexiapi/app/Console/Commands/ImportExternalAccounts.php @@ -0,0 +1,67 @@ +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; + } +} diff --git a/flexiapi/app/ExternalAccount.php b/flexiapi/app/ExternalAccount.php new file mode 100644 index 0000000..2331974 --- /dev/null +++ b/flexiapi/app/ExternalAccount.php @@ -0,0 +1,26 @@ +belongsTo('App\Account'); + } + + public function getIdentifierAttribute() + { + return $this->attributes['username'].'@'.$this->attributes['domain']; + } + + public function getResolvedRealmAttribute() + { + return config('app.realm') ?? $this->attributes['domain']; + } +} diff --git a/flexiapi/app/Helpers/Utils.php b/flexiapi/app/Helpers/Utils.php index 2f40026..a5c6bfb 100644 --- a/flexiapi/app/Helpers/Utils.php +++ b/flexiapi/app/Helpers/Utils.php @@ -17,77 +17,97 @@ along with this program. If not, see . */ -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; } diff --git a/flexiapi/app/Http/Controllers/Account/AccountController.php b/flexiapi/app/Http/Controllers/Account/AccountController.php index e9d437a..6d1b37f 100644 --- a/flexiapi/app/Http/Controllers/Account/AccountController.php +++ b/flexiapi/app/Http/Controllers/Account/AccountController.php @@ -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') ]); } diff --git a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php index bb9bf36..cc0beec 100644 --- a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php +++ b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php @@ -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; diff --git a/flexiapi/app/Http/Controllers/Account/PasswordController.php b/flexiapi/app/Http/Controllers/Account/PasswordController.php index 86e418b..ef7cc3a 100644 --- a/flexiapi/app/Http/Controllers/Account/PasswordController.php +++ b/flexiapi/app/Http/Controllers/Account/PasswordController.php @@ -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 diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index b6f5986..eed3445 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -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', 'identifier.'>'); + $entry = $dom->createElement('entry', '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', '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 diff --git a/flexiapi/app/Http/Controllers/Account/RegisterController.php b/flexiapi/app/Http/Controllers/Account/RegisterController.php index 853bcf2..840c516 100644 --- a/flexiapi/app/Http/Controllers/Account/RegisterController.php +++ b/flexiapi/app/Http/Controllers/Account/RegisterController.php @@ -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; diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php index f2ee354..7e0a247 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php @@ -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); diff --git a/flexiapi/app/Http/Controllers/Api/AccountPhoneController.php b/flexiapi/app/Http/Controllers/Api/AccountPhoneController.php index 3ad6b7b..b790af9 100644 --- a/flexiapi/app/Http/Controllers/Api/AccountPhoneController.php +++ b/flexiapi/app/Http/Controllers/Api/AccountPhoneController.php @@ -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]); diff --git a/flexiapi/app/Http/Controllers/Api/ApiController.php b/flexiapi/app/Http/Controllers/Api/ApiController.php index b151893..a6bcece 100644 --- a/flexiapi/app/Http/Controllers/Api/ApiController.php +++ b/flexiapi/app/Http/Controllers/Api/ApiController.php @@ -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') ]); } } diff --git a/flexiapi/app/Http/Controllers/Api/PasswordController.php b/flexiapi/app/Http/Controllers/Api/PasswordController.php index 6d9a39d..c6c9848 100644 --- a/flexiapi/app/Http/Controllers/Api/PasswordController.php +++ b/flexiapi/app/Http/Controllers/Api/PasswordController.php @@ -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); diff --git a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php index 8f0c951..c4af902 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php +++ b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php @@ -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)) { diff --git a/flexiapi/app/Password.php b/flexiapi/app/Password.php index 826b67d..5e0223c 100644 --- a/flexiapi/app/Password.php +++ b/flexiapi/app/Password.php @@ -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() diff --git a/flexiapi/app/Providers/HelperServiceProvider.php b/flexiapi/app/Providers/HelperServiceProvider.php new file mode 100644 index 0000000..c7ff801 --- /dev/null +++ b/flexiapi/app/Providers/HelperServiceProvider.php @@ -0,0 +1,18 @@ +=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", diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index 2fd4596..fddc407 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -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, ], diff --git a/flexiapi/database/factories/ExternalAccountFactory.php b/flexiapi/database/factories/ExternalAccountFactory.php new file mode 100644 index 0000000..d4cbf48 --- /dev/null +++ b/flexiapi/database/factories/ExternalAccountFactory.php @@ -0,0 +1,25 @@ +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', + ]; + } +} diff --git a/flexiapi/database/factories/PhoneChangeCodeFactory.php b/flexiapi/database/factories/PhoneChangeCodeFactory.php index bde9eff..f8cfc81 100644 --- a/flexiapi/database/factories/PhoneChangeCodeFactory.php +++ b/flexiapi/database/factories/PhoneChangeCodeFactory.php @@ -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', ]; } diff --git a/flexiapi/database/migrations/2022_06_29_100221_create_external_accounts_table.php b/flexiapi/database/migrations/2022_06_29_100221_create_external_accounts_table.php new file mode 100644 index 0000000..0ffdf69 --- /dev/null +++ b/flexiapi/database/migrations/2022_06_29_100221_create_external_accounts_table.php @@ -0,0 +1,42 @@ +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'); + } +} diff --git a/flexiapi/resources/views/account/home.blade.php b/flexiapi/resources/views/account/home.blade.php index 005fdde..8f7c2cf 100644 --- a/flexiapi/resources/views/account/home.blade.php +++ b/flexiapi/resources/views/account/home.blade.php @@ -14,7 +14,7 @@
- @if (config('app.public_registration')) + @if (publicRegistrationEnabled())
Create an account
diff --git a/flexiapi/resources/views/account/login.blade.php b/flexiapi/resources/views/account/login.blade.php index b0d01ec..3f45de7 100644 --- a/flexiapi/resources/views/account/login.blade.php +++ b/flexiapi/resources/views/account/login.blade.php @@ -28,7 +28,7 @@ @include('parts.password_recovery') @endif - @if (config('app.public_registration')) + @if (publicRegistrationEnabled())

diff --git a/flexiapi/resources/views/admin/account/index.blade.php b/flexiapi/resources/views/admin/account/index.blade.php index 6342c3f..a41e0d8 100644 --- a/flexiapi/resources/views/admin/account/index.blade.php +++ b/flexiapi/resources/views/admin/account/index.blade.php @@ -42,14 +42,17 @@ + @if ($account->externalAccount) + EA + @endif @if ($account->email) Email @endif @if ($account->activated) - Activated + Act. @endif @if ($account->admin) - Admin + Adm. @endif @if ($account->sha256Password) SHA256 diff --git a/flexiapi/resources/views/admin/account/show.blade.php b/flexiapi/resources/views/admin/account/show.blade.php index 2fa1763..b0e4d91 100644 --- a/flexiapi/resources/views/admin/account/show.blade.php +++ b/flexiapi/resources/views/admin/account/show.blade.php @@ -22,6 +22,7 @@ Email: {{ $account->email }}
DTMF Protocol: @if ($account->dtmf_protocol) {{ $account->resolvedDtmfProtocol }}@endif
@if ($account->alias)Phone number: {{ $account->phone }}
@endif + @if ($account->group)Group: {{ $account->group }}
@endif @if ($account->display_name)Display name: {{ $account->display_name }}
@endif

@@ -45,6 +46,16 @@ Not Admin Add admin role @endif +

External Account

+ +@if ($account->externalAccount) +

+ Identifier: {{ $account->externalAccount->identifier }}
+

+@else + Attach an External Account ({{ $external_accounts_count}} left) +@endif +

Contacts

diff --git a/flexiapi/resources/views/admin/statistics/parts/columns.blade.php b/flexiapi/resources/views/admin/statistics/parts/columns.blade.php index b62f166..67e1585 100644 --- a/flexiapi/resources/views/admin/statistics/parts/columns.blade.php +++ b/flexiapi/resources/views/admin/statistics/parts/columns.blade.php @@ -1,12 +1,12 @@ -
-
-
-
\ No newline at end of file diff --git a/flexiapi/resources/views/layouts/main.blade.php b/flexiapi/resources/views/layouts/main.blade.php index 65b64d5..a8e8676 100644 --- a/flexiapi/resources/views/layouts/main.blade.php +++ b/flexiapi/resources/views/layouts/main.blade.php @@ -17,7 +17,7 @@