mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 01:58:07 +00:00
Fix #139 Allow the dictionary to be set when creating an account on the API as an admin
This commit is contained in:
parent
0597db0f8e
commit
6226e867ad
12 changed files with 138 additions and 87 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
v1.5
|
||||
----
|
||||
- Fix #138 Add a dictionary attached to the accounts
|
||||
- Fix #137 Migrate the icons from Material Icons to Material Symbols
|
||||
- Fix #135 Refactor the password algorithms code
|
||||
- Fix #134 Create an Activity view in the Admin > Accounts panel
|
||||
|
|
|
|||
|
|
@ -157,6 +157,22 @@ class Account extends Authenticatable
|
|||
});
|
||||
}
|
||||
|
||||
public function setDictionaryEntry(string $key, string $value): AccountDictionaryEntry
|
||||
{
|
||||
$entry = $this->dictionaryEntries->where('key', $key)->first();
|
||||
|
||||
if (!$entry) {
|
||||
$entry = new AccountDictionaryEntry;
|
||||
}
|
||||
|
||||
$entry->account_id = $this->id;
|
||||
$entry->key = $key;
|
||||
$entry->value = $value;
|
||||
$entry->save();
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
public function nonces()
|
||||
{
|
||||
return $this->hasMany(DigestNonce::class);
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ class AccountDictionaryController extends Controller
|
|||
'value' => 'required'
|
||||
]);
|
||||
|
||||
$entry = new AccountDictionaryEntry;
|
||||
$entry->account_id = $account->id;
|
||||
$entry->key = $request->get('key');
|
||||
$entry->value = $request->get('value');
|
||||
$entry->save();
|
||||
$account->setDictionaryEntry($request->get('key'), $request->get('value'));
|
||||
|
||||
return redirect()->route('admin.account.dictionary.index', $account->id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,12 @@ class AccountController extends Controller
|
|||
$actionvationExpiration->save();
|
||||
}
|
||||
|
||||
if ($request->has('dictionary')) {
|
||||
foreach ($request->get('dictionary') as $key => $value) {
|
||||
$account->setDictionaryEntry($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$account->updatePassword($request->get('password'), $request->get('algorithm'));
|
||||
$account->admin = $request->has('admin') && (bool)$request->get('admin');
|
||||
$account->phone = $request->get('phone');
|
||||
|
|
|
|||
|
|
@ -43,18 +43,7 @@ class AccountDictionaryController extends Controller
|
|||
'value' => 'required'
|
||||
]);
|
||||
|
||||
$entry = Account::findOrFail($accountId)->dictionaryEntries()->where('key', $key)->first();
|
||||
|
||||
if (!$entry) {
|
||||
$entry = new AccountDictionaryEntry;
|
||||
}
|
||||
|
||||
$entry->account_id = $accountId;
|
||||
$entry->key = $key;
|
||||
$entry->value = $request->get('value');
|
||||
$entry->save();
|
||||
|
||||
return $entry;
|
||||
return Account::findOrFail($accountId)->setDictionaryEntry($key, $request->get('value'));
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $accountId, string $key)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Illuminate\Validation\Rule;
|
|||
|
||||
use App\Account;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\Dictionary;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
use App\Rules\SIPUsername;
|
||||
|
|
@ -36,6 +37,7 @@ class CreateAccountRequest extends FormRequest
|
|||
}),*/
|
||||
'filled',
|
||||
],
|
||||
'dictionary' => [new Dictionary],
|
||||
'password' => 'required|min:3',
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'nullable|email|unique:accounts,email'
|
||||
|
|
|
|||
24
flexiapi/app/Rules/Dictionary.php
Normal file
24
flexiapi/app/Rules/Dictionary.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class Dictionary implements Rule
|
||||
{
|
||||
public function passes($attribute, $array): bool
|
||||
{
|
||||
if (!is_array($array)) return false;
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if (!is_string($key) || !is_string($value)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return 'The dictionary must be an assiocative dictionary of strings';
|
||||
}
|
||||
}
|
||||
42
flexiapi/composer.lock
generated
42
flexiapi/composer.lock
generated
|
|
@ -3570,23 +3570,23 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.29",
|
||||
"version": "9.2.30",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76"
|
||||
"reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76",
|
||||
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
|
||||
"reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"nikic/php-parser": "^4.15",
|
||||
"nikic/php-parser": "^4.18 || ^5.0",
|
||||
"php": ">=7.3",
|
||||
"phpunit/php-file-iterator": "^3.0.3",
|
||||
"phpunit/php-text-template": "^2.0.2",
|
||||
|
|
@ -3636,7 +3636,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3644,7 +3644,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-09-19T04:57:46+00:00"
|
||||
"time": "2023-12-22T06:47:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
|
@ -5572,20 +5572,20 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/complexity",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/complexity.git",
|
||||
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
|
||||
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
|
||||
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
|
||||
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nikic/php-parser": "^4.7",
|
||||
"nikic/php-parser": "^4.18 || ^5.0",
|
||||
"php": ">=7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
@ -5617,7 +5617,7 @@
|
|||
"homepage": "https://github.com/sebastianbergmann/complexity",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/complexity/issues",
|
||||
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
|
||||
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5625,7 +5625,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-26T15:52:27+00:00"
|
||||
"time": "2023-12-22T06:19:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
|
|
@ -5899,20 +5899,20 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/lines-of-code",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
|
||||
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
|
||||
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
|
||||
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
|
||||
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nikic/php-parser": "^4.6",
|
||||
"nikic/php-parser": "^4.18 || ^5.0",
|
||||
"php": ">=7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
@ -5944,7 +5944,7 @@
|
|||
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
|
||||
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
|
||||
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5952,7 +5952,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-11-28T06:42:11+00:00"
|
||||
"time": "2023-12-22T06:20:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/object-enumerator",
|
||||
|
|
|
|||
1
flexiapi/public/css/style.css
vendored
1
flexiapi/public/css/style.css
vendored
|
|
@ -174,6 +174,7 @@ code {
|
|||
}
|
||||
|
||||
p>a:not(.btn),
|
||||
li>a,
|
||||
table tr td a:not(.btn):hover,
|
||||
label>a {
|
||||
text-decoration: underline;
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ JSON parameters:
|
|||
* `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`, `sipmessage` or `rfc2833`
|
||||
* `dictionary` optional, an associative array attached to the account, <a href="#dictionary">see also the related endpoints</a>.
|
||||
* <span class="badge badge-message">Deprecated</span> `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`.
|
||||
|
||||
### `PUT /accounts/{id}`
|
||||
|
|
|
|||
|
|
@ -152,7 +152,6 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
'password' => '123',
|
||||
'account_creation_token' => $token->token
|
||||
]);
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors(['username']);
|
||||
|
||||
// Blacklisted regex username
|
||||
|
|
@ -163,7 +162,6 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
'account_creation_token' => $token->token
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors(['username']);
|
||||
|
||||
// Valid username
|
||||
|
|
|
|||
|
|
@ -343,6 +343,53 @@ class ApiAccountTest extends TestCase
|
|||
$this->assertFalse(empty($response1['provisioning_token']));
|
||||
}
|
||||
|
||||
public function testAdminWithDictionary()
|
||||
{
|
||||
$admin = Admin::factory()->create();
|
||||
$password = $admin->account->passwords()->first();
|
||||
$password->account->generateApiKey();
|
||||
|
||||
$entryKey = 'foo';
|
||||
$entryValue = 'bar';
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => 'john',
|
||||
'domain' => 'lennon.com',
|
||||
'password' => 'password123',
|
||||
'algorithm' => 'SHA-256',
|
||||
'dictionary' => [
|
||||
$entryKey => $entryValue
|
||||
]
|
||||
])
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'dictionary' => [
|
||||
$entryKey => $entryValue
|
||||
]
|
||||
]);
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => 'john2',
|
||||
'domain' => 'lennon.com',
|
||||
'password' => 'password123',
|
||||
'algorithm' => 'SHA-256',
|
||||
'dictionary' => [
|
||||
$entryKey => ['hey' => 'hop']
|
||||
]
|
||||
])->assertJsonValidationErrors(['dictionary']);
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => 'john2',
|
||||
'domain' => 'lennon.com',
|
||||
'password' => 'password123',
|
||||
'algorithm' => 'SHA-256',
|
||||
'dictionary' => 'hop'
|
||||
])->assertJsonValidationErrors(['dictionary']);
|
||||
}
|
||||
|
||||
public function testActivated()
|
||||
{
|
||||
$admin = Admin::factory()->create();
|
||||
|
|
@ -534,9 +581,7 @@ class ApiAccountTest extends TestCase
|
|||
'domain' => 'server.com',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
])->assertJsonValidationErrors(['email']);
|
||||
}
|
||||
|
||||
public function testNonAsciiPasswordAdmin()
|
||||
|
|
@ -580,7 +625,6 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json('PUT', $this->route . '/1234')
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['username']);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
|
|
@ -707,23 +751,19 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['account_creation_token']);
|
||||
])->assertJsonValidationErrors(['account_creation_token']);
|
||||
|
||||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone,
|
||||
'account_creation_token' => 'wrong'
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['account_creation_token']);
|
||||
])->assertJsonValidationErrors(['account_creation_token']);
|
||||
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone,
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(200);
|
||||
])->assertStatus(200);
|
||||
|
||||
$password->account->refresh();
|
||||
|
||||
|
|
@ -731,8 +771,7 @@ class ApiAccountTest extends TestCase
|
|||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone,
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(422);
|
||||
])->assertStatus(422);
|
||||
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/recover/' . $password->account->confirmation_key)
|
||||
->assertStatus(200)
|
||||
|
|
@ -751,7 +790,6 @@ class ApiAccountTest extends TestCase
|
|||
->assertStatus(404);
|
||||
|
||||
$this->json('GET', $this->route . '/' . $password->account->identifier . '/info-by-phone')
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
|
||||
// Check the mixed username/phone resolution...
|
||||
|
|
@ -788,18 +826,14 @@ class ApiAccountTest extends TestCase
|
|||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
])->assertJsonValidationErrors(['email']);
|
||||
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['account_creation_token']);
|
||||
])->assertJsonValidationErrors(['account_creation_token']);
|
||||
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
$userAgent = 'User Agent Test';
|
||||
|
|
@ -827,8 +861,7 @@ class ApiAccountTest extends TestCase
|
|||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(422);
|
||||
])->assertStatus(422);
|
||||
|
||||
// Already created
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
|
|
@ -836,9 +869,7 @@ class ApiAccountTest extends TestCase
|
|||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['username']);
|
||||
])->assertJsonValidationErrors(['username']);
|
||||
|
||||
// Email is now unique
|
||||
config()->set('app.account_email_unique', true);
|
||||
|
|
@ -848,9 +879,7 @@ class ApiAccountTest extends TestCase
|
|||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
])->assertJsonValidationErrors(['email']);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'username' => $username,
|
||||
|
|
@ -876,9 +905,7 @@ class ApiAccountTest extends TestCase
|
|||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
])->assertJsonValidationErrors(['phone']);
|
||||
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
|
|
@ -900,9 +927,7 @@ class ApiAccountTest extends TestCase
|
|||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
])->assertJsonValidationErrors(['phone']);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'username' => $phone,
|
||||
|
|
@ -1001,9 +1026,7 @@ class ApiAccountTest extends TestCase
|
|||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route . '/me/email/request', [
|
||||
'email' => $otherAccount->account->email
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
])->assertJsonValidationErrors(['email']);
|
||||
}
|
||||
|
||||
public function testChangePassword()
|
||||
|
|
@ -1020,9 +1043,7 @@ class ApiAccountTest extends TestCase
|
|||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => '123',
|
||||
'password' => $password
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['algorithm']);
|
||||
])->assertJsonValidationErrors(['algorithm']);
|
||||
|
||||
// Fresh password without an old one
|
||||
$this->keyAuthenticated($account)
|
||||
|
|
@ -1048,9 +1069,7 @@ class ApiAccountTest extends TestCase
|
|||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => $newAlgorithm,
|
||||
'password' => $newPassword
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['old_password']);
|
||||
])->assertJsonValidationErrors(['old_password']);
|
||||
|
||||
// Set the new password with incorrect old password
|
||||
$this->keyAuthenticated($account)
|
||||
|
|
@ -1058,9 +1077,7 @@ class ApiAccountTest extends TestCase
|
|||
'algorithm' => $newAlgorithm,
|
||||
'old_password' => 'blabla',
|
||||
'password' => $newPassword
|
||||
])
|
||||
->assertJsonValidationErrors(['old_password'])
|
||||
->assertStatus(422);
|
||||
])->assertJsonValidationErrors(['old_password']);
|
||||
|
||||
// Set the new password
|
||||
$this->keyAuthenticated($account)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue