Move the DTMF protocol from AccountAction to Account

Ensure that the account actions are not reachable if the account doesn't have the DTMF protocol configured
Update the documentation
Update the tests
Fix migration for SQLite
This commit is contained in:
Timothée Jaussoin 2022-01-19 17:59:08 +01:00
parent f16776e29b
commit 717d3e3cc9
22 changed files with 202 additions and 169 deletions

View file

@ -1,10 +1,10 @@
$(eval GIT_DESCRIBE = $(shell sh -c "git describe"))
OUTPUT_DIR = ${CURDIR}
prepare:
cd flexiapi && composer install --no-dev
cd flexiapi && php composer.phar install --no-dev
prepare-dev:
cd flexiapi && composer install
cd flexiapi && php composer.phar install
package-common:
rm -rf $(OUTPUT_DIR)/flexisip-account-manager

View file

@ -31,6 +31,7 @@ use App\Password;
use App\EmailChanged;
use App\Helpers\Utils;
use App\Mail\ChangingEmail;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Account extends Authenticatable
{
@ -45,6 +46,8 @@ class Account extends Authenticatable
];
public $timestamps = false;
public static $dtmfProtocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833'];
/**
* Scopes
*/
@ -79,7 +82,11 @@ class Account extends Authenticatable
*/
public function actions()
{
return $this->hasMany('App\AccountAction');
return $this->hasMany('App\AccountAction')->whereIn('account_id', function ($query) {
$query->select('id')
->from('accounts')
->whereNotNull('dtmf_protocol');
});
}
public function activationExpiration()
@ -173,6 +180,16 @@ class Account extends Authenticatable
return $this->passwords()->where('algorithm', 'SHA-256')->exists();
}
public static function dtmfProtocolsRule()
{
return implode(',', array_keys(self::$dtmfProtocols));
}
public function getResolvedDtmfProtocolAttribute()
{
return self::$dtmfProtocols[$this->attributes['dtmf_protocol']];
}
/**
* Utils
*/
@ -249,6 +266,11 @@ FN:'.$this->attributes['display_name'];
FN:'.$this->getIdentifierAttribute();
}
if ($this->dtmf_protocol) {
$vcard .= '
X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:'.$this->dtmf_protocol;
}
if ($this->types->count() > 0) {
$vcard .= '
X-LINPHONE-ACCOUNT-TYPE:'.$this->types->implode('key', ',');
@ -256,7 +278,7 @@ X-LINPHONE-ACCOUNT-TYPE:'.$this->types->implode('key', ',');
foreach ($this->actions as $action) {
$vcard .= '
X-LINPHONE-ACCOUNT-ACTION:'.$action->key.';'.$action->code.';'.$action->protocol;
X-LINPHONE-ACCOUNT-ACTION:'.$action->key.';'.$action->code;
}
return $vcard . '

View file

@ -9,18 +9,6 @@ class AccountAction extends Model
{
use HasFactory;
public static $protocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833'];
public static function protocolsRule()
{
return implode(',', array_keys(self::$protocols));
}
public function getResolvedProtocolAttribute()
{
return self::$protocols[$this->attributes['protocol']];
}
public function account()
{
return $this->belongsTo('App\Account');

View file

@ -35,8 +35,7 @@ class AccountActionController extends Controller
return view('admin.account.action.create_edit', [
'action' => new AccountAction,
'account' => $account,
'protocols' => AccountAction::$protocols
'account' => $account
]);
}
@ -46,15 +45,13 @@ class AccountActionController extends Controller
$request->validate([
'key' => ['required', 'alpha_dash', new NoUppercase],
'code' => ['required', 'alpha_num', new NoUppercase],
'protocol' => 'required|in:' . AccountAction::protocolsRule()
'code' => ['required', 'alpha_num', new NoUppercase]
]);
$accountAction = new AccountAction;
$accountAction->account_id = $account->id;
$accountAction->key = $request->get('key');
$accountAction->code = $request->get('code');
$accountAction->protocol = $request->get('protocol');
$accountAction->save();
$request->session()->flash('success', 'Action successfully created');
@ -73,8 +70,7 @@ class AccountActionController extends Controller
return view('admin.account.action.create_edit', [
'action' => $accountAction,
'account' => $account,
'protocols' => AccountAction::$protocols
'account' => $account
]);
}
@ -84,8 +80,7 @@ class AccountActionController extends Controller
$request->validate([
'key' => ['alpha_dash', new NoUppercase],
'code' => ['alpha_num', new NoUppercase],
'protocol' => 'in:' . AccountAction::protocolsRule()
'code' => ['alpha_num', new NoUppercase]
]);
$accountAction = $account->actions()
@ -93,7 +88,6 @@ class AccountActionController extends Controller
->firstOrFail();
$accountAction->key = $request->get('key');
$accountAction->code = $request->get('code');
$accountAction->protocol = $request->get('protocol');
$accountAction->save();
$request->session()->flash('success', 'Action successfully updated');

View file

@ -58,7 +58,8 @@ class AccountController extends Controller
public function create(Request $request)
{
return view('admin.account.create_edit', [
'account' => new Account
'account' => new Account,
'protocols' => [null => 'None'] + Account::$dtmfProtocols
]);
}
@ -74,6 +75,7 @@ class AccountController extends Controller
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->save();
$this->fillPassword($request, $account);
@ -87,7 +89,8 @@ class AccountController extends Controller
public function edit(int $id)
{
return view('admin.account.create_edit', [
'account' => Account::findOrFail($id)
'account' => Account::findOrFail($id),
'protocols' => [null => 'None'] + Account::$dtmfProtocols
]);
}
@ -97,6 +100,7 @@ class AccountController extends Controller
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->display_name = $request->get('display_name');
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->save();
$this->fillPassword($request, $account);
@ -201,20 +205,16 @@ class AccountController extends Controller
return redirect()->back();
}
private function fillPassword(Request $request, int $id)
private function fillPassword(Request $request, Account $account)
{
$account = Account::findOrFail($id);
if ($request->filled('password')) {
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
$account->updatePassword($request->get('password'), $algorithm);
}
}
private function fillPhone(Request $request, int $id)
private function fillPhone(Request $request, Account $account)
{
$account = Account::findOrFail($id);
if ($request->filled('phone')) {
$account->alias()->delete();

View file

@ -67,6 +67,7 @@ class AccountController extends Controller
],
'algorithm' => 'required|in:SHA-256,MD5',
'password' => 'required|filled',
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
'domain' => 'min:3',
'token' => [
'required',
@ -91,6 +92,7 @@ class AccountController extends Controller
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize);
$account->save();

View file

@ -30,12 +30,12 @@ class AccountActionController extends Controller
{
public function index(int $id)
{
return Account::findOrFail($id)->actions;
return $this->resolveAccount($id)->actions;
}
public function get(int $id, int $actionId)
{
return Account::findOrFail($id)
return $this->resolveAccount($id)
->actions()
->where('id', $actionId)
->firstOrFail();
@ -43,17 +43,17 @@ class AccountActionController extends Controller
public function store(Request $request, int $id)
{
$account = $this->resolveAccount($id);
$request->validate([
'key' => ['required', 'alpha_dash', new NoUppercase],
'code' => ['required', 'alpha_num', new NoUppercase],
'protocol' => 'required|in:' . AccountAction::protocolsRule()
'code' => ['required', 'alpha_num', new NoUppercase]
]);
$accountAction = new AccountAction;
$accountAction->account_id = Account::findOrFail($id)->id;
$accountAction->account_id = $account->id;
$accountAction->key = $request->get('key');
$accountAction->code = $request->get('code');
$accountAction->protocol = $request->get('protocol');
$accountAction->save();
return $accountAction;
@ -61,19 +61,19 @@ class AccountActionController extends Controller
public function update(Request $request, int $id, int $actionId)
{
$account = $this->resolveAccount($id);
$request->validate([
'key' => ['alpha_dash', new NoUppercase],
'code' => ['alpha_num', new NoUppercase],
'protocol' => 'in:' . AccountAction::protocolsRule()
'code' => ['alpha_num', new NoUppercase]
]);
$accountAction = Account::findOrFail($id)
$accountAction = $account
->actions()
->where('id', $actionId)
->firstOrFail();
$accountAction->key = $request->get('key');
$accountAction->code = $request->get('code');
$accountAction->protocol = $request->get('protocol');
$accountAction->save();
return $accountAction;
@ -81,9 +81,17 @@ class AccountActionController extends Controller
public function destroy(int $id, int $actionId)
{
return Account::findOrFail($id)
return $this->resolveAccount($id)
->actions()
->where('id', $actionId)
->delete();
}
private function resolveAccount(int $id)
{
$account = Account::findOrFail($id);
if ($account->dtmf_protocol == null) abort(403, 'DTMF Protocol must be configured');
return $account;
}
}

View file

@ -108,6 +108,7 @@ class AccountController extends Controller
'email' => 'email',
'admin' => 'boolean|nullable',
'activated' => 'boolean|nullable',
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
'confirmation_key_expires' => [
'date_format:Y-m-d H:i:s',
'nullable',
@ -127,6 +128,7 @@ class AccountController extends Controller
? (bool)$request->get('activated')
: false;
$account->ip_address = $request->ip();
$account->dtmf_protocol = $request->get('dtmf_protocol');
$account->creation_time = Carbon::now();
$account->domain = $request->has('domain') && config('app.admins_manage_multi_domains')
? $request->get('domain')

View file

@ -3,8 +3,9 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use App\Account;
use App\Rules\WithoutSpaces;
class CreateAccountRequest extends FormRequest
@ -27,6 +28,7 @@ class CreateAccountRequest extends FormRequest
'domain' => config('app.admins_manage_multi_domains') ? 'required' : '',
'password' => 'required|min:3',
'email' => 'nullable|email',
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
'phone' => [
'nullable',
'unique:aliases,alias',

View file

@ -3,8 +3,9 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use App\Account;
use App\Rules\WithoutSpaces;
class UpdateAccountRequest extends FormRequest
@ -27,6 +28,7 @@ class UpdateAccountRequest extends FormRequest
'domain' => config('app.admins_manage_multi_domains') ? 'required' : '',
'email' => 'nullable|email',
'password_sha256' => 'nullable|min:3',
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
'phone' => [
'nullable',
Rule::unique('accounts', 'username')->where(function ($query) {

113
flexiapi/composer.lock generated
View file

@ -183,79 +183,6 @@
],
"time": "2021-08-15T20:50:18+00:00"
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.4",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
"reference": "b174585d1fe49ceed21928a945138948cb394600"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
"reference": "b174585d1fe49ceed21928a945138948cb394600",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.1.0 || ^2.0",
"php": "^7 || ^8"
},
"replace": {
"ocramius/package-versions": "1.11.99"
},
"require-dev": {
"composer/composer": "^1.9.3 || ^2.0@dev",
"ext-zip": "^1.13",
"phpunit/phpunit": "^6.5 || ^7"
},
"type": "composer-plugin",
"extra": {
"class": "PackageVersions\\Installer",
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"PackageVersions\\": "src/PackageVersions"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be"
}
],
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-09-13T08:41:34+00:00"
},
{
"name": "dasprid/enum",
"version": "1.0.3",
@ -404,20 +331,20 @@
},
{
"name": "doctrine/dbal",
"version": "3.2.1",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "4caf37acf14b513a91dd4f087f7eda424fa25542"
"reference": "a4b37db6f186b6843474189b424aed6a7cc5de4b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/4caf37acf14b513a91dd4f087f7eda424fa25542",
"reference": "4caf37acf14b513a91dd4f087f7eda424fa25542",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/a4b37db6f186b6843474189b424aed6a7cc5de4b",
"reference": "a4b37db6f186b6843474189b424aed6a7cc5de4b",
"shasum": ""
},
"require": {
"composer/package-versions-deprecated": "^1.11.99",
"composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.0",
@ -428,13 +355,13 @@
"require-dev": {
"doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2021.1",
"phpstan/phpstan": "1.3.0",
"phpstan/phpstan": "1.4.0",
"phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "9.5.11",
"psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.16.1"
},
"suggest": {
@ -495,7 +422,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.2.1"
"source": "https://github.com/doctrine/dbal/tree/3.3.0"
},
"funding": [
{
@ -511,7 +438,7 @@
"type": "tidelift"
}
],
"time": "2022-01-05T08:52:06+00:00"
"time": "2022-01-18T00:13:52+00:00"
},
{
"name": "doctrine/deprecations",
@ -819,16 +746,16 @@
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.3.0",
"version": "v3.3.1",
"source": {
"type": "git",
"url": "https://github.com/dragonmantank/cron-expression.git",
"reference": "63f2a76a045bac6ec93cc2daf2b534b412aa0313"
"reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/63f2a76a045bac6ec93cc2daf2b534b412aa0313",
"reference": "63f2a76a045bac6ec93cc2daf2b534b412aa0313",
"url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/be85b3f05b46c39bbc0d95f6c071ddff669510fa",
"reference": "be85b3f05b46c39bbc0d95f6c071ddff669510fa",
"shasum": ""
},
"require": {
@ -868,7 +795,7 @@
],
"support": {
"issues": "https://github.com/dragonmantank/cron-expression/issues",
"source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.0"
"source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.1"
},
"funding": [
{
@ -876,7 +803,7 @@
"type": "github"
}
],
"time": "2022-01-14T16:02:05+00:00"
"time": "2022-01-18T15:43:28+00:00"
},
{
"name": "egulias/email-validator",
@ -1455,16 +1382,16 @@
},
{
"name": "laravel/framework",
"version": "v8.79.0",
"version": "v8.80.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "8091f07558ff4a890435ff9d25fa9aca0189ad63"
"reference": "8949a2e46b0f274f39c61eee8d5de1dc6a1f686b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/8091f07558ff4a890435ff9d25fa9aca0189ad63",
"reference": "8091f07558ff4a890435ff9d25fa9aca0189ad63",
"url": "https://api.github.com/repos/laravel/framework/zipball/8949a2e46b0f274f39c61eee8d5de1dc6a1f686b",
"reference": "8949a2e46b0f274f39c61eee8d5de1dc6a1f686b",
"shasum": ""
},
"require": {
@ -1624,7 +1551,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2022-01-12T16:12:41+00:00"
"time": "2022-01-18T15:51:42+00:00"
},
{
"name": "laravel/serializable-closure",

Binary file not shown.

View file

@ -38,6 +38,7 @@ class AccountFactory extends Factory
'confirmation_key' => Str::random(WebAuthenticateController::$emailCodeSize),
'ip_address' => $this->faker->ipv4,
'creation_time' => $this->faker->dateTime,
'dtmf_protocol' => Account::$dtmfProtocols[array_rand(Account::$dtmfProtocols)],
'activated' => true
];
}

View file

@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class CreateContactsTable extends Migration
{
@ -47,7 +48,15 @@ class CreateContactsTable extends Migration
->on('accounts')->onDelete('cascade');
$table->string('key');
$table->string('code');
$table->string('protocol');
/**
* See 2022_01_19_160606_move_protocol_from_account_actions_to_account.php
* SQLite can't handle the migration in the testing pipeline, so we must
* prevent the column to be created in the first place
**/
if (DB::getDriverName() !== 'sqlite') {
$table->string('protocol');
}
$table->timestamps();
});
}

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class MoveProtocolFromAccountActionsToAccount extends Migration
{
public function up()
{
Schema::table('accounts', function (Blueprint $table) {
$table->string('dtmf_protocol')->nullable();
});
// See 2021_10_13_092937_create_contacts_table.php
Schema::table('account_actions', function (Blueprint $table) {
if (DB::getDriverName() !== 'sqlite') {
$table->dropColumn('protocol');
}
});
}
public function down()
{
Schema::table('accounts', function (Blueprint $table) {
if (DB::getDriverName() !== 'sqlite') {
$table->dropColumn('dtmf_protocol');
}
});
Schema::table('account_actions', function (Blueprint $table) {
$table->string('protocol');
});
}
}

View file

@ -37,10 +37,6 @@
{!! Form::label('code', 'Code') !!}
{!! Form::text('code', $action->code, ['class' => 'form-control', 'placeholder' => '12ab45']); !!}
</div>
<div class="form-group col-md-12">
{!! Form::label('protocol', 'Protocol') !!}
{!! Form::select('protocol', $protocols, $action->protocol, ['class' => 'form-control']); !!}
</div>
</div>
{!! Form::submit(($action->id) ? 'Update' : 'Create', ['class' => 'btn btn-success btn-centered']) !!}

View file

@ -43,7 +43,7 @@
<div class="input-group-append">
<span class="input-group-text" id="basic-addon1">@</span>
</div>
{!! Form::text('username', $account->domain ?? config('app.sip_domain'), ['class' => 'form-control', 'placeholder' => 'domain.com', 'required' => 'required']); !!}
{!! Form::text('domain', $account->domain ?? config('app.sip_domain'), ['class' => 'form-control', 'placeholder' => 'domain.com', 'required' => 'required']); !!}
@else
<div class="input-group-append">
<span class="input-group-text" id="basic-addon1">@ {{ config('app.sip_domain') }}</span>
@ -70,6 +70,7 @@
<div class="form-group col-md-12 mb-0">
<h4>Optional</h4>
</div>
<div class="form-group col-md-6">
{!! Form::label('email', 'Email') !!}
{!! Form::email('email', $account->email, ['class' => 'form-control', 'placeholder' => 'Email']); !!}
@ -84,6 +85,11 @@
{!! Form::label('phone', 'Phone') !!}
{!! Form::text('phone', $account->phone, ['class' => 'form-control', 'placeholder' => '+12123123']); !!}
</div>
<div class="form-group col-md-6">
{!! Form::label('dtmf_protocol', 'DTMF Protocol') !!}
{!! Form::select('dtmf_protocol', $protocols, $account->dtmf_protocol, ['class' => 'form-control']); !!}
</div>
</div>
{!! Form::submit(($account->id) ? 'Update' : 'Create', ['class' => 'btn btn-success btn-centered']) !!}

View file

@ -20,6 +20,7 @@
<b>Id:</b> {{ $account->id }}<br />
<b>Identifier:</b> {{ $account->identifier }}<br />
<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->display_name)<b>Display name:</b> {{ $account->display_name }}<br />@endif
</p>
@ -63,13 +64,14 @@
<h3 class="mt-3">Actions</h3>
@if (!$account->dtmf_protocol)
<table class="table">
<tbody>
@foreach ($account->actions as $action)
<tr>
<th scope="row">{{ $action->key }}</th>
<td>{{ $action->code }}</td>
<td>{{ $action->resolvedProtocol }}</td>
<td>
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.edit', [$account, $action->id]) }}">Edit</a>
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.delete', [$account, $action->id]) }}">Delete</a>
@ -81,6 +83,10 @@
<a class="btn btn-sm" href="{{ route('admin.account.action.create', $account) }}">Add</a>
@else
<p>To manage actions, you must configure the DTMF protocol in the account settings.</p>
@endif
<h3 class="mt-3">Types</h3>
<table class="table">

View file

@ -82,6 +82,7 @@ JSON parameters:
* `algorithm` required, values can be `SHA-256` or `MD5`
* `domain` **not configurable except during test deployments** the value is enforced to the default registration domain set in the global configuration
* `token` the unique token
* `dtmf_protocol` optional, values must be `sipinfo` or `rfc2833`
#### `GET /accounts/{sip}/info`
Retrieve public information about the account.
@ -179,6 +180,7 @@ The `domain` field is taken into account ONLY when `app.admins_manage_multi_doma
* `display_name` optional, string
* `admin` optional, a boolean, set to `false` by default, create an admin account
* `phone` optional, a phone number, set a phone number to the account
* `dtmf_protocol` optional, values must be `sipinfo` or `rfc2833`
* `confirmation_key_expires` optional, a datetime of this format: Y-m-d H:i:s. Only used when `activated` is not used or `false`. Enforces an expiration date on the returned `confirmation_key`. After that datetime public email or phone activation endpoints will return `403`.
#### `GET /accounts`
@ -212,6 +214,8 @@ Remove a contact from the list.
### Account Actions
The following endpoints will return `403 Forbidden` if the requested account doesn't have a DTMF protocol configured.
#### `GET /accounts/{id}/actions/`
Show an account related actions.
@ -225,7 +229,6 @@ JSON parameters:
* `key` required, alpha numeric with dashes, lowercase
* `code` required, alpha numeric, lowercase
* `protocol` required, values must be `sipinfo` or `rfc2833`
#### `PUT /accounts/{id}/actions/{action_id}`
Create an account action.
@ -234,7 +237,6 @@ JSON parameters:
* `key` required, alpha numeric with dashes, lowercase
* `code` required, alpha numeric, lowercase
* `protocol` required, values must be `sipinfo` or `rfc2833`
#### `DELETE /accounts/{id}/actions/{action_id}`
Delete an account related action.
@ -311,5 +313,26 @@ Return the same base content as the previous URL and the account related informa
### `GET /contacts/vcard`
Return the authenticated user contacts list, in [vCard 4.0 format](https://datatracker.ietf.org/doc/html/rfc6350).
Here is the format of the vCard list returned by the endpoint:
```
BEGIN:VCARD
VERSION:4.0
KIND:individual
IMPP:sip:schoen.tatyana@sip.linphone.org
FN:schoen.tatyana@sip.linphone.org
X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:SIPInfo
X-LINPHONE-ACCOUNT-TYPE:phone
X-LINPHONE-ACCOUNT-ACTION:action_key;123
END:VCARD
BEGIN:VCARD
VERSION:4.0
KIND:individual
IMPP:sip:dhand@sip.linphone.org
FN:dhand@sip.linphone.org
X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:SIPInfo
END:VCARD
```
### `GET /contacts/vcard/{sip}`
Return a specific user authenticated contact, in [vCard 4.0 format](https://datatracker.ietf.org/doc/html/rfc6350).

View file

@ -43,8 +43,7 @@ class AccountActionTest extends TestCase
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route.'/'.$password->account->id.'/actions', [
'key' => '123',
'code' => '123',
'protocol' => 'sipinfo'
'code' => '123'
])
->assertStatus(201);
@ -53,17 +52,7 @@ class AccountActionTest extends TestCase
// Missing key
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route.'/'.$password->account->id.'/actions', [
'code' => '123',
'protocol' => 'sipinfo'
])
->assertStatus(422);
// Invalid protocol
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route.'/'.$password->account->id.'/actions', [
'key' => 'abc1234',
'code' => '123',
'protocol' => 'wrong'
'code' => '123'
])
->assertStatus(422);
@ -71,8 +60,7 @@ class AccountActionTest extends TestCase
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route.'/'.$password->account->id.'/actions', [
'key' => 'Abc1234',
'code' => '123',
'protocol' => 'wrong'
'code' => '123'
])
->assertStatus(422);
@ -81,10 +69,29 @@ class AccountActionTest extends TestCase
->assertJson([
[
'key' => '123',
'code' => '123',
'protocol' => 'sipinfo'
'code' => '123'
]
]);
// No protocol
$password->account->dtmf_protocol = null;
$password->account->save();
$this->keyAuthenticated($admin->account)
->json($this->method, $this->route.'/'.$password->account->id.'/actions', [
'key' => 'abc1234',
'code' => '123'
])
->assertStatus(403);
$this->keyAuthenticated($admin->account)
->get($this->route.'/'.$password->account->id.'/actions')
->assertStatus(403);
$this->keyAuthenticated($admin->account)
->get($this->route.'/'.$password->account->id)
->assertStatus(200)
->assertJsonPath('actions', []);
}
public function testDelete()

View file

@ -121,13 +121,15 @@ class AccountContactTest extends TestCase
->get('/contacts/vcard')
->assertStatus(200)
->assertSeeText($typeKey)
->assertSeeText($actionKey.';'.$actionCode.';'.$actionProtocol);
->assertSeeText($password2->dtmf_protocol)
->assertSeeText($actionKey.';'.$actionCode);
$this->keyAuthenticated($password1->account)
->get('/contacts/vcard/'.$password2->account->identifier)
->assertStatus(200)
->assertSeeText($typeKey)
->assertSeeText($actionKey.';'.$actionCode.';'.$actionProtocol);
->assertSeeText($password2->dtmf_protocol)
->assertSeeText($actionKey.';'.$actionCode);
$this->keyAuthenticated($password1->account)
->get($this->route.'/me/contacts/'.$password2->account->identifier)

View file

@ -8,7 +8,7 @@
#%define _datadir %{_datarootdir}
#%define _docdir %{_datadir}/doc
%define build_number 123
%define build_number 124
%define var_dir /var/opt/belledonne-communications
%define opt_dir /opt/belledonne-communications/share/flexisip-account-manager