Fix FLEXIAPI-149 Add a toggle to disable phone check on username for admin endpoints and forms

This commit is contained in:
Timothée Jaussoin 2024-03-19 15:37:09 +01:00
parent f6ac67b8b1
commit 9d7618e9c4
9 changed files with 56 additions and 17 deletions

View file

@ -5,6 +5,7 @@ v1.5
- Fix FLEXIAPI-153 Add phone and email to be changed in the Activity panel - Fix FLEXIAPI-153 Add phone and email to be changed in the Activity panel
- Fix FLEXIAPI-151 Migrate to hCaptcha - Fix FLEXIAPI-151 Migrate to hCaptcha
- Fix FLEXIAPI-150 Use the same account_id parameter for both API and Web routes - Fix FLEXIAPI-150 Use the same account_id parameter for both API and Web routes
- Fix FLEXIAPI-149 Add a toggle to disable phone check on username for admin endpoints and forms
- Fix FLEXIAPI-148 Reuse AccountService in the POST /api/accounts admin endpoint - Fix FLEXIAPI-148 Reuse AccountService in the POST /api/accounts admin endpoint
- FIX FLEXIAPI-146 Allow users to manage their own devices - FIX FLEXIAPI-146 Allow users to manage their own devices
- Fix FLEXIAPI-145 Put back the 'code' parameter as an alias for the 'confirmation_key' for the activateEmail and activatePhone endpoints - Fix FLEXIAPI-145 Put back the 'code' parameter as an alias for the 'confirmation_key' for the activateEmail and activatePhone endpoints

View file

@ -12,6 +12,8 @@ APP_FLEXISIP_PUSHER_FIREBASE_KEYSMAP= # Each pair is separated using a space and
APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the generated API Keys are valid APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the generated API Keys are valid
APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES=60 # Number of minutes between two consecutive account_creation_token creation APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES=60 # Number of minutes between two consecutive account_creation_token creation
APP_ALLOW_PHONE_NUMBER_USERNAME_ADMIN_API=false # Allow phone numbers to be set as username in admin account creation endpoints
# Risky toggles # Risky toggles
APP_ADMINS_MANAGE_MULTI_DOMAINS=false # Allow admins to handle all the accounts in the database APP_ADMINS_MANAGE_MULTI_DOMAINS=false # Allow admins to handle all the accounts in the database
APP_DANGEROUS_ENDPOINTS=false # Enable some dangerous endpoints used for XMLRPC like fallback usage APP_DANGEROUS_ENDPOINTS=false # Enable some dangerous endpoints used for XMLRPC like fallback usage

View file

@ -26,7 +26,7 @@ use Carbon\Carbon;
use App\Account; use App\Account;
use App\ContactsList; use App\ContactsList;
use App\Http\Requests\CreateAccountRequest; use App\Http\Requests\CreateAccountWithoutUsernamePhoneCheck;
use App\Http\Requests\UpdateAccountRequest; use App\Http\Requests\UpdateAccountRequest;
class AccountController extends Controller class AccountController extends Controller
@ -79,7 +79,7 @@ class AccountController extends Controller
]); ]);
} }
public function store(CreateAccountRequest $request) public function store(CreateAccountWithoutUsernamePhoneCheck $request)
{ {
$request->validate([ $request->validate([
'password' => 'confirmed' 'password' => 'confirmed'

View file

@ -21,17 +21,14 @@ namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use App\Account; use App\Account;
use App\AccountTombstone; use App\AccountTombstone;
use App\AccountType; use App\AccountType;
use App\ActivationExpiration;
use App\ContactsList; use App\ContactsList;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Http\Requests\CreateAccountRequest; use App\Http\Requests\CreateAccountRequest;
use App\Http\Requests\CreateAccountWithoutUsernamePhoneCheck;
use App\Http\Requests\UpdateAccountRequest; use App\Http\Requests\UpdateAccountRequest;
use App\Rules\PasswordAlgorithm; use App\Rules\PasswordAlgorithm;
use App\Services\AccountService; use App\Services\AccountService;
@ -129,7 +126,7 @@ class AccountController extends Controller
return $account->makeVisible(['provisioning_token']); return $account->makeVisible(['provisioning_token']);
} }
public function store(CreateAccountRequest $request) public function store(CreateAccountWithoutUsernamePhoneCheck $request)
{ {
return (new AccountService)->store($request, asAdmin: true)->makeVisible(['confirmation_key', 'provisioning_token']); return (new AccountService)->store($request, asAdmin: true)->makeVisible(['confirmation_key', 'provisioning_token']);
} }

View file

@ -32,9 +32,9 @@ class CreateAccountRequest extends FormRequest
Rule::unique('accounts', 'username')->where(function ($query) { Rule::unique('accounts', 'username')->where(function ($query) {
$query->where('domain', resolveDomain($this)); $query->where('domain', resolveDomain($this));
}), }),
/*Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) { Rule::unique('accounts_tombstones', 'username')->where(function ($query) {
$query->where('domain', config('app.sip_domain')); $query->where('domain', resolveDomain($this));
}),*/ }),
'filled', 'filled',
], ],
'dictionary' => [new Dictionary], 'dictionary' => [new Dictionary],

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use App\Rules\IsNotPhoneNumber;
class CreateAccountWithoutUsernamePhoneCheck extends CreateAccountRequest
{
public function authorize()
{
return true;
}
public function rules()
{
$parentRules = parent::rules();
if (config('app.allow_phone_number_username_admin_api') == true) {
array_splice(
$parentRules['username'],
array_search(new IsNotPhoneNumber(), $parentRules['username']),
1
);
}
return $parentRules;
}
}

View file

@ -31,6 +31,7 @@ return [
'transport_protocol_text' => env('ACCOUNT_TRANSPORT_PROTOCOL_TEXT', 'TLS (recommended), TCP or UDP'), 'transport_protocol_text' => env('ACCOUNT_TRANSPORT_PROTOCOL_TEXT', 'TLS (recommended), TCP or UDP'),
'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false), 'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false),
'allow_phone_number_username_admin_api' => env('APP_ALLOW_PHONE_NUMBER_USERNAME_ADMIN_API', false),
'blacklisted_usernames' => env('ACCOUNT_BLACKLISTED_USERNAMES', ''), 'blacklisted_usernames' => env('ACCOUNT_BLACKLISTED_USERNAMES', ''),
'account_username_regex' => env('ACCOUNT_USERNAME_REGEX', '^[a-z0-9+_.-]*$'), 'account_username_regex' => env('ACCOUNT_USERNAME_REGEX', '^[a-z0-9+_.-]*$'),
'account_default_password_algorithm' => env('ACCOUNT_DEFAULT_PASSWORD_ALGORITHM', 'SHA-256'), 'account_default_password_algorithm' => env('ACCOUNT_DEFAULT_PASSWORD_ALGORITHM', 'SHA-256'),

View file

@ -39,7 +39,7 @@
<h2>Connexion</h2> <h2>Connexion</h2>
<div> <div>
<input placeholder="Username" required="required" name="username" type="text" <input placeholder="Username" required="required" name="username" type="text"
value="@if ($account->id){{ $account->username }}@else{{ old('username') }} @endif" value="@if($account->id){{ $account->username }}@else{{ old('username') }}@endif"
@if ($account->id) readonly @endif> @if ($account->id) readonly @endif>
<label for="username">Username</label> <label for="username">Username</label>
@include('parts.errors', ['name' => 'username']) @include('parts.errors', ['name' => 'username'])
@ -53,7 +53,7 @@
<div> <div>
<input placeholder="John Doe" name="display_name" type="text" <input placeholder="John Doe" name="display_name" type="text"
value="@if ($account->id){{ $account->display_name }}@else{{ old('display_name') }} @endif"> value="@if($account->id){{ $account->display_name }}@else{{ old('display_name') }}@endif">
<label for="display_name">Display Name</label> <label for="display_name">Display Name</label>
@include('parts.errors', ['name' => 'display_name']) @include('parts.errors', ['name' => 'display_name'])
</div> </div>
@ -75,14 +75,14 @@
<div> <div>
<input placeholder="Email" name="email" type="email" <input placeholder="Email" name="email" type="email"
value="@if ($account->id) {{ $account->email }}@else{{ old('email') }} @endif"> value="@if($account->id){{ $account->email }}@else{{ old('email') }}@endif">
<label for="email">Email</label> <label for="email">Email</label>
@include('parts.errors', ['name' => 'email']) @include('parts.errors', ['name' => 'email'])
</div> </div>
<div> <div>
<input placeholder="+12123123" name="phone" type="text" <input placeholder="+12123123" name="phone" type="text"
value="@if ($account->id) {{ $account->phone }}@else{{ old('phone') }} @endif"> value="@if($account->id){{ $account->phone }}@else{{ old('phone') }}@endif">
<label for="phone">Phone</label> <label for="phone">Phone</label>
@include('parts.errors', ['name' => 'phone']) @include('parts.errors', ['name' => 'phone'])
</div> </div>

View file

@ -86,15 +86,25 @@ class ApiAccountTest extends TestCase
$username = '+33612121212'; $username = '+33612121212';
$domain = 'example.com'; $domain = 'example.com';
$response = $this->keyAuthenticated($password->account) $this->keyAuthenticated($password->account)
->json($this->method, $this->route, [ ->json($this->method, $this->route, [
'username' => $username, 'username' => $username,
'domain' => $domain, 'domain' => $domain,
'algorithm' => 'SHA-256', 'algorithm' => 'SHA-256',
'password' => '123456', 'password' => '123456',
]); ])
->assertJsonValidationErrors(['username']);
$response->assertJsonValidationErrors(['username']); config()->set('app.allow_phone_number_username_admin_api', true);
$this->keyAuthenticated($password->account)
->json($this->method, $this->route, [
'username' => $username,
'domain' => $domain,
'algorithm' => 'SHA-256',
'password' => '123456',
])
->assertStatus(200);
} }
public function testUsernameNotSIP() public function testUsernameNotSIP()