mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-18 10:28:07 +00:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c102bcec77 | ||
|
|
25bec44486 | ||
|
|
6555112715 | ||
|
|
30b8e492d8 | ||
|
|
ec1bdba376 | ||
|
|
ca4320e734 | ||
|
|
1e5052da1d | ||
|
|
cf6b007f26 | ||
|
|
6ca20e6a9c | ||
|
|
d2b2a9dd6d | ||
|
|
82b5e967dc | ||
|
|
0d21f3fda9 | ||
|
|
9569c79008 | ||
|
|
e11d55e3f9 | ||
|
|
3f35954071 | ||
|
|
0a8eda05c4 | ||
|
|
c22e713e1a | ||
|
|
9cb5953209 | ||
|
|
233feef9d8 |
56 changed files with 1000 additions and 456 deletions
|
|
@ -28,11 +28,12 @@ remi-deploy:
|
|||
stage: deploy
|
||||
tags: ["docker"]
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
- master
|
||||
- /^release/.*$/
|
||||
|
||||
before_script:
|
||||
- rm -f $CI_PROJECT_DIR/build/*devel*.rpm
|
||||
- rm -f $CI_PROJECT_DIR/build/*devel*.rpm # Remove devel packages
|
||||
- cd $CI_PROJECT_DIR/build/ && for file in *; do mv "$file" $(echo "$file" | sed -e 's/[^A-Za-z0-9._+-]//g'); done || true && cd .. # Rename non standard packages
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$DEPLOY_USER_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ variables:
|
|||
DEBIAN_11_IMAGE_VERSION: 20230322_172926_missing_tools
|
||||
PHP_REDIS_REMI_VERSION: php-pecl-redis5-5.3.6-1
|
||||
PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.14-1
|
||||
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.1.2-1
|
||||
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-1
|
||||
PHP_XMLRPC_REMI_VERSION: php-pecl-xmlrpc-1.0.0~rc3-2
|
||||
|
||||
include:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
v1.3
|
||||
----
|
||||
- Fix #90 Deploy packages from release branches as well
|
||||
- Fix #58 Fix the packaging process to use git describe as a reference
|
||||
- Fix #58 Move the generated packages in the build directory, and fix the release and version format in the .spec
|
||||
- Fix #58 Refactor and cleanup the .gitlab-ci file
|
||||
|
|
@ -14,6 +15,11 @@ v1.3
|
|||
- Fix #79 Add a refresh_password parameter to the provisioning URLs
|
||||
- Fix #78 Add a APP_ACCOUNTS_EMAIL_UNIQUE environnement setting
|
||||
- Fix #30 Remove APP_EVERYONE_IS_ADMIN
|
||||
- Fix #97 Validate usernames with a configurable regex
|
||||
- Fix #95 PUT /accounts admin endpoint implementation
|
||||
- Fix #102 Implement AccountCreationRequestToken
|
||||
- Fix #92 Add two new endpoints regarding email account reset and account search per email for admins
|
||||
- Fix #94 Implement the deprecated endpoint changes + tests + documentation
|
||||
|
||||
v1.2
|
||||
----
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ ACCOUNT_REALM=null # Default realm for the accounts, fallback to the domain if n
|
|||
ACCOUNT_EMAIL_UNIQUE=false # Emails are unique between all the accounts
|
||||
ACCOUNT_CONSUME_EXTERNAL_ACCOUNT_ON_CREATE=false
|
||||
ACCOUNT_BLACKLISTED_USERNAMES=
|
||||
ACCOUNT_USERNAME_REGEX="^[a-z0-9+_.-]*$"
|
||||
|
||||
# Account provisioning
|
||||
ACCOUNT_PROVISIONING_RC_FILE=
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use App\Password;
|
|||
use App\EmailChanged;
|
||||
use App\Mail\ChangingEmail;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Account extends Authenticatable
|
||||
{
|
||||
|
|
@ -108,62 +109,62 @@ class Account extends Authenticatable
|
|||
|
||||
public function activationExpiration()
|
||||
{
|
||||
return $this->hasOne('App\ActivationExpiration');
|
||||
return $this->hasOne(ActivationExpiration::class);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->hasOne('App\Admin');
|
||||
return $this->hasOne(Admin::class);
|
||||
}
|
||||
|
||||
public function alias()
|
||||
{
|
||||
return $this->hasOne('App\Alias');
|
||||
return $this->hasOne(Alias::class);
|
||||
}
|
||||
|
||||
public function apiKey()
|
||||
{
|
||||
return $this->hasOne('App\ApiKey');
|
||||
return $this->hasOne(ApiKey::class);
|
||||
}
|
||||
|
||||
public function externalAccount()
|
||||
{
|
||||
return $this->hasOne('App\ExternalAccount');
|
||||
return $this->hasOne(ExternalAccount::class);
|
||||
}
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->belongsToMany('App\Account', 'contacts', 'account_id', 'contact_id');
|
||||
return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id');
|
||||
}
|
||||
|
||||
public function emailChanged()
|
||||
{
|
||||
return $this->hasOne('App\EmailChanged');
|
||||
return $this->hasOne(EmailChanged::class);
|
||||
}
|
||||
|
||||
public function nonces()
|
||||
{
|
||||
return $this->hasMany('App\DigestNonce');
|
||||
return $this->hasMany(DigestNonce::class);
|
||||
}
|
||||
|
||||
public function authTokens()
|
||||
{
|
||||
return $this->hasMany('App\AuthToken');
|
||||
return $this->hasMany(AuthToken::class);
|
||||
}
|
||||
|
||||
public function passwords()
|
||||
{
|
||||
return $this->hasMany('App\Password');
|
||||
return $this->hasMany(Password::class);
|
||||
}
|
||||
|
||||
public function phoneChangeCode()
|
||||
{
|
||||
return $this->hasOne('App\PhoneChangeCode');
|
||||
return $this->hasOne(PhoneChangeCode::class);
|
||||
}
|
||||
|
||||
public function types()
|
||||
{
|
||||
return $this->belongsToMany('App\AccountType');
|
||||
return $this->belongsToMany(AccountType::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -202,6 +203,19 @@ class Account extends Authenticatable
|
|||
return null;
|
||||
}
|
||||
|
||||
public function setPhoneAttribute(?string $phone)
|
||||
{
|
||||
$this->alias()->delete();
|
||||
|
||||
if (!empty($phone)) {
|
||||
$alias = new Alias;
|
||||
$alias->alias = $phone;
|
||||
$alias->domain = config('app.sip_domain');
|
||||
$alias->account_id = $this->id;
|
||||
$alias->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function getConfirmationKeyExpiresAttribute()
|
||||
{
|
||||
if ($this->activationExpiration) {
|
||||
|
|
@ -302,9 +316,20 @@ class Account extends Authenticatable
|
|||
return $this->provisioning_token;
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
public function getAdminAttribute(): bool
|
||||
{
|
||||
return ($this->admin);
|
||||
return ($this->admin()->exists());
|
||||
}
|
||||
|
||||
public function setAdminAttribute(bool $isAdmin)
|
||||
{
|
||||
$this->admin()->delete();
|
||||
|
||||
if ($isAdmin) {
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $this->id;
|
||||
$admin->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasTombstone()
|
||||
|
|
@ -325,6 +350,14 @@ class Account extends Authenticatable
|
|||
$password->save();
|
||||
}
|
||||
|
||||
public function fillPassword(Request $request)
|
||||
{
|
||||
if ($request->filled('password')) {
|
||||
$this->algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
|
||||
$this->updatePassword($request->get('password'), $this->algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
public function toVcard4()
|
||||
{
|
||||
$vcard = 'BEGIN:VCARD
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ class AccountAction extends Model
|
|||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
flexiapi/app/AccountCreationRequestToken.php
Normal file
43
flexiapi/app/AccountCreationRequestToken.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountCreationRequestToken extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id', 'updated_at', 'created_at'];
|
||||
protected $appends = ['validation_url'];
|
||||
|
||||
public function accountCreationToken()
|
||||
{
|
||||
return $this->belongsTo(AccountCreationToken::class, 'acc_creation_token_id');
|
||||
}
|
||||
|
||||
public function getValidationUrlAttribute(): ?string
|
||||
{
|
||||
return $this->validated_at == null
|
||||
? route('account.creation_request_token.check', $this->token)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,4 +25,11 @@ use Illuminate\Database\Eloquent\Model;
|
|||
class AccountCreationToken extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id', 'updated_at', 'created_at'];
|
||||
|
||||
public function accountCreationRequestToken()
|
||||
{
|
||||
return $this->hasOne(AccountCreationRequestToken::class, 'acc_creation_token_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ class AccountType extends Model
|
|||
|
||||
public function accounts()
|
||||
{
|
||||
return $this->belongsToMany('App\Account');
|
||||
return $this->belongsToMany(Account::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class ActivationExpiration extends Model
|
|||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function isExpired()
|
||||
|
|
|
|||
|
|
@ -30,4 +30,16 @@ class Alias extends Model
|
|||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
}
|
||||
|
||||
public function scopeSip($query, string $sip)
|
||||
{
|
||||
if (\str_contains($sip, '@')) {
|
||||
list($usernane, $domain) = explode('@', $sip);
|
||||
|
||||
return $query->where('alias', $usernane)
|
||||
->where('domain', $domain);
|
||||
};
|
||||
|
||||
return $query->where('id', '<', 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Illuminate\Support\Str;
|
|||
use App\Account;
|
||||
use App\DigestNonce;
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
|
||||
|
|
@ -119,3 +120,13 @@ function isRegularExpression($string): bool
|
|||
|
||||
return $isRegularExpression;
|
||||
}
|
||||
|
||||
function resolveDomain(Request $request): string
|
||||
{
|
||||
return $request->has('domain')
|
||||
&& $request->user()
|
||||
&& $request->user()->admin
|
||||
&& config('app.admins_manage_multi_domains')
|
||||
? $request->get('domain')
|
||||
: config('app.sip_domain');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\AccountCreationRequestToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Rules\AccountCreationRequestToken as RulesAccountCreationRequestToken;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreationRequestTokenController extends Controller
|
||||
{
|
||||
public function check(Request $request, string $creationRequestToken)
|
||||
{
|
||||
$request->merge(['account_creation_request_token' => $creationRequestToken]);
|
||||
$request->validate([
|
||||
'account_creation_request_token' => [
|
||||
'required',
|
||||
new RulesAccountCreationRequestToken
|
||||
]
|
||||
]);
|
||||
|
||||
$accountCreationRequestToken = AccountCreationRequestToken::where('token', $request->get('account_creation_request_token'))->firstOrFail();
|
||||
|
||||
return view('account.creation_request_token.check', [
|
||||
'account_creation_request_token' => $accountCreationRequestToken
|
||||
]);
|
||||
}
|
||||
|
||||
public function validateToken(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'account_creation_request_token' => [
|
||||
'required',
|
||||
new RulesAccountCreationRequestToken
|
||||
],
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
]);
|
||||
|
||||
$accountCreationRequestToken = AccountCreationRequestToken::where('token', $request->get('account_creation_request_token'))->firstOrFail();
|
||||
$accountCreationRequestToken->validated_at = Carbon::now();
|
||||
$accountCreationRequestToken->save();
|
||||
|
||||
return view('account.creation_request_token.valid');
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ namespace App\Http\Controllers\Admin;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
|
@ -31,13 +30,6 @@ use App\Alias;
|
|||
use App\ExternalAccount;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
use App\Rules\SIPUsername;
|
||||
use App\Rules\WithoutSpaces;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
|
@ -73,45 +65,19 @@ class AccountController extends Controller
|
|||
|
||||
public function store(CreateAccountRequest $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => [
|
||||
'required',
|
||||
new NoUppercase,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', $this->resolveDomain($request));
|
||||
}),
|
||||
'filled',
|
||||
],
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email',
|
||||
config('app.account_email_unique') ? Rule::unique('accounts', 'email') : null
|
||||
],
|
||||
'phone' => [
|
||||
'nullable',
|
||||
'unique:aliases,alias',
|
||||
'unique:accounts,username',
|
||||
new WithoutSpaces, 'starts_with:+'
|
||||
]
|
||||
]);
|
||||
|
||||
$account = new Account;
|
||||
$account->username = $request->get('username');
|
||||
$account->email = $request->get('email');
|
||||
$account->display_name = $request->get('display_name');
|
||||
$account->domain = $this->resolveDomain($request);
|
||||
$account->domain = resolveDomain($request);
|
||||
$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);
|
||||
$this->fillPhone($request, $account);
|
||||
$account->phone = $request->get('phone');
|
||||
$account->fillPassword($request);
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]);
|
||||
|
||||
|
|
@ -128,32 +94,6 @@ class AccountController extends Controller
|
|||
|
||||
public function update(UpdateAccountRequest $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => [
|
||||
'required',
|
||||
new NoUppercase,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', $this->resolveDomain($request));
|
||||
})->ignore($id),
|
||||
'filled',
|
||||
],
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email',
|
||||
config('app.account_email_unique') ? Rule::unique('accounts', 'email')->ignore($id) : null
|
||||
],
|
||||
'phone' => [
|
||||
'nullable',
|
||||
'unique:aliases,alias',
|
||||
'unique:accounts,username',
|
||||
new WithoutSpaces, 'starts_with:+'
|
||||
]
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($id);
|
||||
$account->username = $request->get('username');
|
||||
$account->email = $request->get('email');
|
||||
|
|
@ -161,8 +101,8 @@ class AccountController extends Controller
|
|||
$account->dtmf_protocol = $request->get('dtmf_protocol');
|
||||
$account->save();
|
||||
|
||||
$this->fillPassword($request, $account);
|
||||
$this->fillPhone($request, $account);
|
||||
$account->phone = $request->get('phone');
|
||||
$account->fillPassword($request);
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account updated', ['id' => $account->identifier]);
|
||||
|
||||
|
|
@ -264,25 +204,4 @@ class AccountController extends Controller
|
|||
|
||||
return redirect()->route('admin.account.index');
|
||||
}
|
||||
|
||||
private function fillPassword(Request $request, Account $account)
|
||||
{
|
||||
if ($request->filled('password')) {
|
||||
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
|
||||
$account->updatePassword($request->get('password'), $algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
private function fillPhone(Request $request, Account $account)
|
||||
{
|
||||
if ($request->filled('phone')) {
|
||||
$account->alias()->delete();
|
||||
|
||||
$alias = new Alias;
|
||||
$alias->alias = $request->get('phone');
|
||||
$alias->domain = config('app.sip_domain');
|
||||
$alias->account_id = $account->id;
|
||||
$alias->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
|
@ -34,6 +34,7 @@ use App\Alias;
|
|||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use App\Libraries\OvhSMS;
|
||||
use App\Mail\RegisterConfirmation;
|
||||
use App\Rules\AccountCreationToken as RulesAccountCreationToken;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
|
|
@ -70,11 +71,13 @@ class AccountController extends Controller
|
|||
$alias = Alias::where('alias', $phone)->first();
|
||||
$account = $alias
|
||||
? $alias->account
|
||||
: Account::sip($phone)->firstOrFail();
|
||||
// Injecting the default sip domain to try to resolve the account
|
||||
: Account::sip($phone . '@' . config('app.sip_domain'))->firstOrFail();
|
||||
|
||||
return \response()->json([
|
||||
'activated' => $account->activated,
|
||||
'realm' => $account->realm
|
||||
'realm' => $account->realm,
|
||||
'phone' => (bool)$alias
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -88,9 +91,8 @@ class AccountController extends Controller
|
|||
|
||||
$request->validate([
|
||||
'username' => [
|
||||
'prohibits:phone',
|
||||
'required_without:phone',
|
||||
new NoUppercase,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
|
|
@ -109,10 +111,14 @@ class AccountController extends Controller
|
|||
: 'required_without:phone|email',
|
||||
'phone' => [
|
||||
'required_without:email',
|
||||
'prohibits:username',
|
||||
'required_without:username',
|
||||
'unique:aliases,alias',
|
||||
'unique:accounts,username',
|
||||
new WithoutSpaces, 'starts_with:+'
|
||||
],
|
||||
'account_creation_token' => [
|
||||
'required',
|
||||
new RulesAccountCreationToken
|
||||
]
|
||||
]);
|
||||
|
||||
|
|
@ -127,7 +133,7 @@ class AccountController extends Controller
|
|||
: config('app.sip_domain');
|
||||
$account->ip_address = $request->ip();
|
||||
$account->creation_time = Carbon::now();
|
||||
$account->user_agent = config('app.name');
|
||||
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
|
||||
$account->provision();
|
||||
$account->save();
|
||||
|
||||
|
|
@ -178,13 +184,17 @@ class AccountController extends Controller
|
|||
$request->validate([
|
||||
'phone' => [
|
||||
'required', new WithoutSpaces, 'starts_with:+'
|
||||
],
|
||||
'account_creation_token' => [
|
||||
'required',
|
||||
new RulesAccountCreationToken
|
||||
]
|
||||
]);
|
||||
|
||||
$alias = Alias::where('alias', $request->get('phone'))->first();
|
||||
$account = $alias
|
||||
? $alias->account
|
||||
: Account::sip($request->get('phone'))->firstOrFail();
|
||||
: Account::sip($request->get('phone') . '@' . config('app.sip_domain'))->firstOrFail();
|
||||
|
||||
$account->confirmation_key = generatePin();
|
||||
$account->save();
|
||||
|
|
@ -202,9 +212,12 @@ class AccountController extends Controller
|
|||
{
|
||||
if (!config('app.dangerous_endpoints')) return abort(404);
|
||||
|
||||
$account = Account::sip($sip)
|
||||
->where('confirmation_key', $recoveryKey)
|
||||
->firstOrFail();
|
||||
$alias = Alias::sip($sip)->first();
|
||||
$account = $alias
|
||||
? $alias->account
|
||||
: Account::sip($sip)->firstOrFail();
|
||||
|
||||
if ($account->confirmation_key != $recoveryKey) abort(404);
|
||||
|
||||
if ($account->activationExpired()) abort(403, 'Activation expired');
|
||||
|
||||
|
|
@ -240,26 +253,15 @@ class AccountController extends Controller
|
|||
'password' => 'required|filled',
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'account_creation_token' => [
|
||||
'required_without:token',
|
||||
Rule::exists('account_creation_tokens', 'token')->where(function ($query) {
|
||||
$query->where('used', false);
|
||||
}),
|
||||
'size:' . WebAuthenticateController::$emailCodeSize
|
||||
'required',
|
||||
new RulesAccountCreationToken
|
||||
],
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'nullable|email|unique:accounts,email'
|
||||
: 'nullable|email',
|
||||
// For retro-compatibility
|
||||
'token' => [
|
||||
'required_without:account_creation_token',
|
||||
Rule::exists('account_creation_tokens', 'token')->where(function ($query) {
|
||||
$query->where('used', false);
|
||||
}),
|
||||
'size:' . WebAuthenticateController::$emailCodeSize
|
||||
],
|
||||
]);
|
||||
|
||||
$token = AccountCreationToken::where('token', $request->get('token') ?? $request->get('account_creation_token'))->first();
|
||||
$token = AccountCreationToken::where('token', $request->get('account_creation_token'))->first();
|
||||
$token->used = true;
|
||||
$token->save();
|
||||
|
||||
|
|
@ -285,12 +287,17 @@ class AccountController extends Controller
|
|||
|
||||
public function activateEmail(Request $request, string $sip)
|
||||
{
|
||||
// For retro-compatibility
|
||||
if ($request->has('code')) {
|
||||
$request->merge(['confirmation_key' => $request->get('code')]);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'code' => 'required|size:' . WebAuthenticateController::$emailCodeSize
|
||||
'confirmation_key' => 'required|size:' . WebAuthenticateController::$emailCodeSize
|
||||
]);
|
||||
|
||||
$account = Account::sip($sip)
|
||||
->where('confirmation_key', $request->get('code'))
|
||||
->where('confirmation_key', $request->get('confirmation_key'))
|
||||
->firstOrFail();
|
||||
|
||||
if ($account->activationExpired()) abort(403, 'Activation expired');
|
||||
|
|
@ -306,12 +313,17 @@ class AccountController extends Controller
|
|||
|
||||
public function activatePhone(Request $request, string $sip)
|
||||
{
|
||||
// For retro-compatibility
|
||||
if ($request->has('code')) {
|
||||
$request->merge(['confirmation_key' => $request->get('code')]);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'code' => 'required|digits:4'
|
||||
'confirmation_key' => 'required|digits:4'
|
||||
]);
|
||||
|
||||
$account = Account::sip($sip)
|
||||
->where('confirmation_key', $request->get('code'))
|
||||
->where('confirmation_key', $request->get('confirmation_key'))
|
||||
->firstOrFail();
|
||||
|
||||
if ($account->activationExpired()) abort(403, 'Activation expired');
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\AuthToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\AuthToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
|
@ -17,13 +17,13 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class AccountContactController extends Controller
|
||||
class ContactController extends Controller
|
||||
{
|
||||
private $selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol'];
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\AccountCreationRequestToken;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class CreationRequestToken extends Controller
|
||||
{
|
||||
public function create(Request $request)
|
||||
{
|
||||
$creationRequestToken = new AccountCreationRequestToken;
|
||||
$creationRequestToken->token = Str::random(WebAuthenticateController::$emailCodeSize);
|
||||
$creationRequestToken->save();
|
||||
|
||||
return $creationRequestToken;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,8 +17,9 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\AccountCreationRequestToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
@ -27,8 +28,9 @@ use Illuminate\Support\Facades\Log;
|
|||
use App\AccountCreationToken;
|
||||
use App\Libraries\FlexisipPusherConnector;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use App\Rules\AccountCreationRequestToken as RulesAccountCreationRequestToken;
|
||||
|
||||
class AccountCreationTokenController extends Controller
|
||||
class CreationTokenController extends Controller
|
||||
{
|
||||
public function sendByPush(Request $request)
|
||||
{
|
||||
|
|
@ -55,4 +57,32 @@ class AccountCreationTokenController extends Controller
|
|||
|
||||
abort(503, "Token not sent");
|
||||
}
|
||||
|
||||
public function usingAccountRequestToken(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'account_creation_request_token' => [
|
||||
'required',
|
||||
new RulesAccountCreationRequestToken
|
||||
]
|
||||
]);
|
||||
|
||||
$creationRequestToken = AccountCreationRequestToken::where('token', $request->get('account_creation_request_token'))
|
||||
->where('used', false)
|
||||
->first();
|
||||
|
||||
if ($creationRequestToken && $creationRequestToken->validated_at != null) {
|
||||
$accountCreationToken = new AccountCreationToken;
|
||||
$accountCreationToken->token = Str::random(WebAuthenticateController::$emailCodeSize);
|
||||
$accountCreationToken->save();
|
||||
|
||||
$creationRequestToken->used = true;
|
||||
$creationRequestToken->acc_creation_token_id = $accountCreationToken->id;
|
||||
$creationRequestToken->save();
|
||||
|
||||
return $accountCreationToken;
|
||||
}
|
||||
|
||||
return abort(404);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -29,7 +29,7 @@ use App\Libraries\OvhSMS;
|
|||
use App\PhoneChangeCode;
|
||||
use App\Alias;
|
||||
|
||||
class AccountPhoneController extends Controller
|
||||
class PhoneController extends Controller
|
||||
{
|
||||
public function requestUpdate(Request $request)
|
||||
{
|
||||
|
|
@ -22,7 +22,6 @@ namespace App\Http\Controllers\Api\Admin;
|
|||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
|
@ -33,11 +32,10 @@ use App\ActivationExpiration;
|
|||
use App\Admin;
|
||||
use App\Alias;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
use App\Rules\SIPUsername;
|
||||
use App\Rules\WithoutSpaces;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
use App\Mail\PasswordAuthentication;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
|
@ -56,6 +54,11 @@ class AccountController extends Controller
|
|||
return Account::sip($sip)->firstOrFail();
|
||||
}
|
||||
|
||||
public function searchByEmail(Request $request, string $email)
|
||||
{
|
||||
return Account::where('email', $email)->firstOrFail();
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
|
|
@ -105,36 +108,15 @@ class AccountController extends Controller
|
|||
return $account->makeVisible(['provisioning_token']);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
public function store(CreateAccountRequest $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => [
|
||||
'required',
|
||||
new NoUppercase,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', $this->resolveDomain($request));
|
||||
}),
|
||||
'filled',
|
||||
],
|
||||
'algorithm' => 'required|in:SHA-256,MD5',
|
||||
'password' => 'required|filled',
|
||||
'admin' => 'boolean|nullable',
|
||||
'activated' => 'boolean|nullable',
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'confirmation_key_expires' => [
|
||||
'date_format:Y-m-d H:i:s',
|
||||
'nullable',
|
||||
],
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'nullable|email|unique:accounts,email'
|
||||
: 'nullable|email',
|
||||
'phone' => [
|
||||
'unique:aliases,alias',
|
||||
'unique:accounts,username',
|
||||
new WithoutSpaces, 'starts_with:+'
|
||||
]
|
||||
]);
|
||||
|
||||
|
|
@ -146,7 +128,7 @@ class AccountController extends Controller
|
|||
$account->ip_address = $request->ip();
|
||||
$account->dtmf_protocol = $request->get('dtmf_protocol');
|
||||
$account->creation_time = Carbon::now();
|
||||
$account->domain = $this->resolveDomain($request);
|
||||
$account->domain = resolveDomain($request);
|
||||
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
|
||||
|
||||
if (!$request->has('activated') || !(bool)$request->get('activated')) {
|
||||
|
|
@ -165,27 +147,45 @@ class AccountController extends Controller
|
|||
}
|
||||
|
||||
$account->updatePassword($request->get('password'), $request->get('algorithm'));
|
||||
|
||||
if ($request->has('admin') && (bool)$request->get('admin')) {
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $account->id;
|
||||
$admin->save();
|
||||
}
|
||||
|
||||
if ($request->has('phone')) {
|
||||
$alias = new Alias;
|
||||
$alias->alias = $request->get('phone');
|
||||
$alias->domain = config('app.sip_domain');
|
||||
$alias->account_id = $account->id;
|
||||
$alias->save();
|
||||
}
|
||||
$account->admin = $request->has('admin') && (bool)$request->get('admin');
|
||||
$account->phone = $request->get('phone');
|
||||
|
||||
// Full reload
|
||||
$account = Account::withoutGlobalScopes()->find($account->id);
|
||||
|
||||
Log::channel('events')->info('API: Admin: Account created', ['id' => $account->identifier]);
|
||||
Log::channel('events')->info('API Admin: Account created', ['id' => $account->identifier]);
|
||||
|
||||
return response()->json($account->makeVisible(['confirmation_key', 'provisioning_token']));
|
||||
return $account->makeVisible(['confirmation_key', 'provisioning_token']);
|
||||
}
|
||||
|
||||
public function update(UpdateAccountRequest $request, int $accountId)
|
||||
{
|
||||
$request->validate([
|
||||
'algorithm' => 'required|in:SHA-256,MD5',
|
||||
'admin' => 'boolean|nullable',
|
||||
'activated' => 'boolean|nullable'
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($accountId);
|
||||
$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->domain = resolveDomain($request);
|
||||
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
|
||||
|
||||
$account->save();
|
||||
|
||||
$account->updatePassword($request->get('password'), $request->get('algorithm'));
|
||||
$account->admin = $request->has('admin') && (bool)$request->get('admin');
|
||||
$account->phone = $request->get('phone');
|
||||
|
||||
// Full reload
|
||||
$account = Account::withoutGlobalScopes()->find($account->id);
|
||||
|
||||
Log::channel('events')->info('API Admin: Account updated', ['id' => $account->identifier]);
|
||||
|
||||
return $account->makeVisible(['confirmation_key', 'provisioning_token']);
|
||||
}
|
||||
|
||||
public function typeAdd(int $id, int $typeId)
|
||||
|
|
@ -207,4 +207,18 @@ class AccountController extends Controller
|
|||
|
||||
return Account::findOrFail($id)->types()->detach($typeId);
|
||||
}
|
||||
|
||||
public function recoverByEmail(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->provision();
|
||||
$account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize);
|
||||
$account->save();
|
||||
|
||||
Log::channel('events')->info('API Admin: Sending recovery email', ['id' => $account->identifier]);
|
||||
|
||||
Mail::to($account)->send(new PasswordAuthentication($account));
|
||||
|
||||
return $account->makeVisible(['confirmation_key', 'provisioning_token']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class AccountTypeController extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'key' => ['required', 'alpha_dash', new NoUppercase],
|
||||
'key' => ['required', 'alpha_dash', new NoUppercase, 'unique:account_types,key'],
|
||||
]);
|
||||
|
||||
$accountType = new AccountType;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
|
|
@ -11,14 +11,4 @@ use Illuminate\Routing\Controller as BaseController;
|
|||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
protected function resolveDomain(Request $request): string
|
||||
{
|
||||
return $request->has('domain')
|
||||
&& $request->user()
|
||||
&& $request->user()->isAdmin()
|
||||
&& config('app.admins_manage_multi_domains')
|
||||
? $request->get('domain')
|
||||
: config('app.sip_domain');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class AuthenticateAdmin
|
|||
return redirect()->route('account.login');
|
||||
}
|
||||
|
||||
if (!$request->user()->isAdmin()) {
|
||||
if (!$request->user()->admin) {
|
||||
return abort(403, 'Unauthorized area');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,14 @@ class CreateAccountRequest extends FormRequest
|
|||
new BlacklistedUsername,
|
||||
new SIPUsername,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) {
|
||||
$query->where('domain', config('app.sip_domain'));
|
||||
$query->where('domain', resolveDomain($this));
|
||||
}),
|
||||
'filled',
|
||||
],
|
||||
'domain' => config('app.admins_manage_multi_domains') ? 'required' : '',
|
||||
'password' => 'required|min:3',
|
||||
'email' => 'nullable|email',
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'nullable|email|unique:accounts,email'
|
||||
: 'nullable|email',
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'phone' => [
|
||||
'nullable',
|
||||
|
|
|
|||
|
|
@ -33,8 +33,11 @@ class UpdateAccountRequest extends FormRequest
|
|||
})->ignore($this->route('id'), 'id'),
|
||||
'filled',
|
||||
],
|
||||
'domain' => config('app.admins_manage_multi_domains') ? 'required' : '',
|
||||
'email' => 'nullable|email',
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email',
|
||||
config('app.account_email_unique') ? Rule::unique('accounts', 'email')->ignore($this->route('id')) : null
|
||||
],
|
||||
'password_sha256' => 'nullable|min:3',
|
||||
'dtmf_protocol' => 'nullable|in:' . Account::dtmfProtocolsRule(),
|
||||
'phone' => [
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ class OvhSMS
|
|||
'validityPeriod' => 2880
|
||||
];
|
||||
|
||||
Log::channel('events')->info('OVH SMS sending', ['to' => $to, 'message' => $message]);
|
||||
|
||||
try {
|
||||
$this->api->post('/sms/'. $this->smsService . '/jobs', $content);
|
||||
// One credit removed
|
||||
|
|
|
|||
38
flexiapi/app/Rules/AccountCreationRequestToken.php
Normal file
38
flexiapi/app/Rules/AccountCreationRequestToken.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\AccountCreationRequestToken as AppAccountCreationRequestToken;
|
||||
use App\Http\Controllers\Account\AuthenticateController;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class AccountCreationRequestToken implements Rule
|
||||
{
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return AppAccountCreationRequestToken::where('token', $value)->where('used', false)->exists()
|
||||
&& strlen($value) == AuthenticateController::$emailCodeSize;
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return 'Please provide a valid account_creation_request_token';
|
||||
}
|
||||
}
|
||||
38
flexiapi/app/Rules/AccountCreationToken.php
Normal file
38
flexiapi/app/Rules/AccountCreationToken.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\AccountCreationToken as AppAccountCreationToken;
|
||||
use App\Http\Controllers\Account\AuthenticateController;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class AccountCreationToken implements Rule
|
||||
{
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return AppAccountCreationToken::where('token', $value)->where('used', false)->exists()
|
||||
&& strlen($value) == AuthenticateController::$emailCodeSize;
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return 'Please provide a valid account_creation_token';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,21 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class SIPUsername implements Rule
|
|||
{
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return Validator::regex('/^[a-z0-9+_.-]*$/')->validate($value);
|
||||
return Validator::regex('/' . config('app.account_username_regex') . '/')->validate($value);
|
||||
}
|
||||
|
||||
public function message()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ return [
|
|||
'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false),
|
||||
'consume_external_account_on_create' => env('ACCOUNT_CONSUME_EXTERNAL_ACCOUNT_ON_CREATE', false),
|
||||
'blacklisted_usernames' => env('ACCOUNT_BLACKLISTED_USERNAMES', ''),
|
||||
'account_username_regex' => env('ACCOUNT_USERNAME_REGEX', '^[a-z0-9+_.-]*$'),
|
||||
|
||||
/**
|
||||
* Time limit before the API Key and related cookie are expired
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddEmailIndexToAccountsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->index('email');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->dropIndex('accounts_email_index');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use App\AccountCreationToken;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MakePnAttributesNullableAccountCreationTokensTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('account_creation_tokens', function (Blueprint $table) {
|
||||
$table->string('pn_provider')->nullable(true)->change();
|
||||
$table->string('pn_param')->nullable(true)->change();
|
||||
$table->string('pn_prid')->nullable(true)->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
AccountCreationToken::whereNull('pn_provider')->delete();
|
||||
|
||||
Schema::table('account_creation_tokens', function (Blueprint $table) {
|
||||
$table->string('pn_provider')->nullable(false)->change();
|
||||
$table->string('pn_param')->nullable(false)->change();
|
||||
$table->string('pn_prid')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateAccountCreationRequestTokensTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('account_creation_request_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('token', 16)->index();
|
||||
$table->boolean('used')->default(false);
|
||||
$table->dateTime('validated_at')->nullable();
|
||||
|
||||
$table->bigInteger('acc_creation_token_id')->unsigned()->nullable();
|
||||
$table->foreign('acc_creation_token_id')->references('id')
|
||||
->on('account_creation_tokens')->onDelete('cascade');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('account_creation_request_tokens');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Handle Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
|
||||
RewriteBase /flexiapi/
|
||||
</IfModule>
|
||||
4
flexiapi/public/css/style.css
vendored
4
flexiapi/public/css/style.css
vendored
|
|
@ -29,6 +29,10 @@ body > div {
|
|||
max-width: 800px;
|
||||
}
|
||||
|
||||
.container.large {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
body > footer::before {
|
||||
background-color: white;
|
||||
background-position: bottom center;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
{!! Form::open(['route' => 'account.creation_request_token.validate']) !!}
|
||||
{!! Form::hidden('account_creation_request_token', $account_creation_request_token->token) !!}
|
||||
@include('parts.captcha')
|
||||
{!! Form::submit('I\'m not a robot', ['class' => 'btn btn-primary btn-centered']) !!}
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<h3 class="text-center mt-5">Thanks for the validation</h3>
|
||||
<p class="text-center">You can now continue your registration process in the application</p>
|
||||
@endsection
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
@if($account->isAdmin())
|
||||
@if($account->admin)
|
||||
<h3>Admin area</h3>
|
||||
<div class="list-group mb-3">
|
||||
<a href="{{ route('admin.account.index') }}" class="list-group-item list-group-item-action">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.main')
|
||||
@extends('layouts.main', ['large' => true])
|
||||
|
||||
@section('content')
|
||||
{{-- This view is only a wrapper for the markdown page --}}
|
||||
|
|
|
|||
|
|
@ -78,9 +78,19 @@ You can find more documentation on the related [IETF RFC-7616](https://tools.iet
|
|||
<span class="badge badge-success">Public</span>
|
||||
Returns `pong`
|
||||
|
||||
## Account Creation Request Tokens
|
||||
|
||||
An `account_creation_request_token` is a unique token that can be validated and then used to generate a valid `account_creation_token`.
|
||||
|
||||
### `POST /account_creation_request_tokens`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
||||
Create and return an `account_creation_request_token` that should then be validated to be used.
|
||||
|
||||
|
||||
## Account Creation Tokens
|
||||
|
||||
An account creation token is a unique token that allow the creation of a **unique** account.
|
||||
An `account_creation_token` is a unique token that allow the creation of a **unique** account.
|
||||
|
||||
### `POST /account_creation_tokens/send-by-push`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
|
@ -94,6 +104,16 @@ JSON parameters:
|
|||
* `pn_param` the push notification parameter
|
||||
* `pn_prid` the push notification unique id
|
||||
|
||||
### `POST /account_creation_tokens/using-account-creation-request-token`
|
||||
<span class="badge badge-success">Public</span>
|
||||
Create an `account_creation_token` using an `account_creation_request_token`.
|
||||
Return an `account_creation_token`.
|
||||
Return `404` if the `account_creation_request_token` provided is not valid or expired otherwise.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `account_creation_request_token` required
|
||||
|
||||
### `POST /account_creation_tokens`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
|
|
@ -134,6 +154,7 @@ JSON parameters:
|
|||
* `domain` if not set the value is enforced to the default registration domain set in the global configuration
|
||||
* `email` optional if `phone` set, an email, set an email to the account, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true`
|
||||
* `phone` required if `username` not set, optional if `email` set, a phone number, set a phone number to the account
|
||||
* `account_creation_token` the unique `account_creation_token`
|
||||
|
||||
### `POST /accounts/with-account-creation-token`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
|
@ -146,7 +167,7 @@ JSON parameters:
|
|||
* `password` required minimum 6 characters
|
||||
* `algorithm` required, values can be `SHA-256` or `MD5`
|
||||
* `account_creation_token` the unique `account_creation_token`
|
||||
* `dtmf_protocol` optional, values must be `sipinfo` or `rfc2833`
|
||||
* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833`
|
||||
|
||||
### `GET /accounts/{sip}/info`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
|
@ -162,6 +183,8 @@ Return `404` if the account doesn't exists.
|
|||
Retrieve public information about the account.
|
||||
Return `404` if the account doesn't exists.
|
||||
|
||||
Return `phone: true` if the returned account has a phone number.
|
||||
|
||||
### `POST /accounts/recover-by-phone`
|
||||
@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
|
||||
|
||||
|
|
@ -174,6 +197,7 @@ Return `404` if the account doesn't exists.
|
|||
JSON parameters:
|
||||
|
||||
* `phone` required the phone number to send the SMS to
|
||||
* `account_creation_token` the unique `account_creation_token`
|
||||
|
||||
### `GET /accounts/{sip}/recover/{recover_key}`
|
||||
@if(config('app.dangerous_endpoints'))**Enabled on this instance**@else**Not enabled on this instance**@endif
|
||||
|
|
@ -182,6 +206,9 @@ JSON parameters:
|
|||
<span class="badge badge-success">Public</span>
|
||||
<span class="badge badge-warning">Unsecure endpoint</span>
|
||||
Activate the account if the correct `recover_key` is provided.
|
||||
|
||||
The `sip` parameter can be the default SIP account or the phone based one.
|
||||
|
||||
Return the account information (including the hashed password) if valid.
|
||||
|
||||
Return `404` if the account doesn't exists.
|
||||
|
|
@ -192,7 +219,7 @@ Activate an account using a secret code received by email.
|
|||
Return `404` if the account doesn't exists or if the code is incorrect, the validated account otherwise.
|
||||
JSON parameters:
|
||||
|
||||
* `code` the code
|
||||
* `confirmation_key` the confirmation key
|
||||
|
||||
### `POST /accounts/{sip}/activate/phone`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
|
@ -200,7 +227,7 @@ Activate an account using a pin code received by phone.
|
|||
Return `404` if the account doesn't exists or if the code is incorrect, the validated account otherwise.
|
||||
JSON parameters:
|
||||
|
||||
* `code` the PIN code
|
||||
* `confirmation_key` the PIN code
|
||||
|
||||
### `GET /accounts/me/api_key/{auth_token}`
|
||||
<span class="badge badge-success">Public</span>
|
||||
|
|
@ -261,9 +288,24 @@ JSON parameters:
|
|||
* `email` optional, must be an email, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true`
|
||||
* `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`
|
||||
* `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` 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`.
|
||||
|
||||
### `PUT /accounts/{id}`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Update an existing account.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `username` unique username, minimum 6 characters
|
||||
* `password` required minimum 6 characters
|
||||
* `algorithm` required, values can be `SHA-256` or `MD5`
|
||||
* `display_name` optional, string
|
||||
* `email` optional, must be an email, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true`
|
||||
* `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`
|
||||
|
||||
### `GET /accounts`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Retrieve all the accounts, paginated.
|
||||
|
|
@ -272,10 +314,18 @@ Retrieve all the accounts, paginated.
|
|||
<span class="badge badge-warning">Admin</span>
|
||||
Retrieve a specific account.
|
||||
|
||||
### `POST /accounts/{id}/recover-by-email`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Send the account recovery email containing a fresh `provisioning_token` and `confirmation_key`
|
||||
|
||||
### `GET /accounts/{sip}/search`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Search for a specific account by sip address.
|
||||
|
||||
### `GET /accounts/{email}/search-by-email`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Search for a specific account by email.
|
||||
|
||||
### `DELETE /accounts/{id}`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Delete a specific account and its related information.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
@endsection
|
||||
|
||||
@section('body')
|
||||
<div class="container pt-4">
|
||||
<div class="container @if (isset($large) && $large) large @endif pt-4">
|
||||
@include('parts.errors')
|
||||
@yield('content')
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,69 +26,74 @@ Route::middleware('auth:api')->get('/user', function (Request $request) {
|
|||
});
|
||||
|
||||
Route::get('ping', 'Api\PingController@ping');
|
||||
Route::post('account_creation_tokens/send-by-push', 'Api\AccountCreationTokenController@sendByPush');
|
||||
// Old URL, for retro-compatibility
|
||||
Route::post('tokens', 'Api\AccountCreationTokenController@sendByPush');
|
||||
|
||||
Route::get('accounts/{sip}/info', 'Api\AccountController@info');
|
||||
Route::post('account_creation_request_tokens', 'Api\Account\CreationRequestToken@create');
|
||||
Route::post('account_creation_tokens/send-by-push', 'Api\Account\CreationTokenController@sendByPush');
|
||||
Route::post('account_creation_tokens/using-account-creation-request-token', 'Api\Account\CreationTokenController@usingAccountRequestToken');
|
||||
Route::post('accounts/with-account-creation-token', 'Api\Account\AccountController@store');
|
||||
|
||||
Route::post('accounts/with-account-creation-token', 'Api\AccountController@store');
|
||||
// Old URL, for retro-compatibility
|
||||
Route::post('accounts/with-token', 'Api\AccountController@store');
|
||||
Route::get('accounts/{sip}/info', 'Api\Account\AccountController@info');
|
||||
|
||||
Route::post('accounts/{sip}/activate/email', 'Api\AccountController@activateEmail');
|
||||
Route::post('accounts/{sip}/activate/phone', 'Api\AccountController@activatePhone');
|
||||
Route::post('accounts/{sip}/activate/email', 'Api\Account\AccountController@activateEmail');
|
||||
Route::post('accounts/{sip}/activate/phone', 'Api\Account\AccountController@activatePhone');
|
||||
|
||||
// /!\ Dangerous endpoints
|
||||
Route::post('accounts/public', 'Api\AccountController@storePublic');
|
||||
Route::get('accounts/{sip}/recover/{recovery_key}', 'Api\AccountController@recoverUsingKey');
|
||||
Route::post('accounts/recover-by-phone', 'Api\AccountController@recoverByPhone');
|
||||
Route::get('accounts/{phone}/info-by-phone', 'Api\AccountController@phoneInfo');
|
||||
Route::post('accounts/public', 'Api\Account\AccountController@storePublic');
|
||||
Route::get('accounts/{sip}/recover/{recovery_key}', 'Api\Account\AccountController@recoverUsingKey');
|
||||
Route::post('accounts/recover-by-phone', 'Api\Account\AccountController@recoverByPhone');
|
||||
Route::get('accounts/{phone}/info-by-phone', 'Api\Account\AccountController@phoneInfo');
|
||||
|
||||
Route::post('accounts/auth_token', 'Api\AuthTokenController@store');
|
||||
Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store');
|
||||
|
||||
Route::get('accounts/me/api_key/{auth_token}', 'Api\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
|
||||
Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
|
||||
|
||||
Route::group(['middleware' => ['auth.digest_or_key']], function () {
|
||||
Route::get('statistic/month', 'Api\StatisticController@month');
|
||||
Route::get('statistic/week', 'Api\StatisticController@week');
|
||||
Route::get('statistic/day', 'Api\StatisticController@day');
|
||||
|
||||
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\AuthTokenController@attach');
|
||||
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
|
||||
|
||||
Route::get('accounts/me/api_key', 'Api\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt');
|
||||
Route::get('accounts/me/api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt');
|
||||
|
||||
Route::get('accounts/me', 'Api\AccountController@show');
|
||||
Route::delete('accounts/me', 'Api\AccountController@delete');
|
||||
Route::get('accounts/me/provision', 'Api\AccountController@provision');
|
||||
Route::get('accounts/me', 'Api\Account\AccountController@show');
|
||||
Route::delete('accounts/me', 'Api\Account\AccountController@delete');
|
||||
Route::get('accounts/me/provision', 'Api\Account\AccountController@provision');
|
||||
|
||||
Route::post('accounts/me/phone/request', 'Api\AccountPhoneController@requestUpdate');
|
||||
Route::post('accounts/me/phone', 'Api\AccountPhoneController@update');
|
||||
Route::post('accounts/me/phone/request', 'Api\Account\PhoneController@requestUpdate');
|
||||
Route::post('accounts/me/phone', 'Api\Account\PhoneController@update');
|
||||
|
||||
Route::get('accounts/me/devices', 'Api\DeviceController@index');
|
||||
Route::delete('accounts/me/devices/{uuid}', 'Api\DeviceController@destroy');
|
||||
Route::get('accounts/me/devices', 'Api\Account\DeviceController@index');
|
||||
Route::delete('accounts/me/devices/{uuid}', 'Api\Account\DeviceController@destroy');
|
||||
|
||||
Route::post('accounts/me/email/request', 'Api\EmailController@requestUpdate');
|
||||
Route::post('accounts/me/password', 'Api\PasswordController@update');
|
||||
Route::post('accounts/me/email/request', 'Api\Account\EmailController@requestUpdate');
|
||||
Route::post('accounts/me/password', 'Api\Account\PasswordController@update');
|
||||
|
||||
Route::get('accounts/me/contacts/{sip}', 'Api\AccountContactController@show');
|
||||
Route::get('accounts/me/contacts', 'Api\AccountContactController@index');
|
||||
Route::get('accounts/me/contacts/{sip}', 'Api\Account\ContactController@show');
|
||||
Route::get('accounts/me/contacts', 'Api\Account\ContactController@index');
|
||||
|
||||
Route::group(['middleware' => ['auth.admin']], function () {
|
||||
if (!empty(config('app.linphone_daemon_unix_pipe'))) {
|
||||
Route::post('messages', 'Api\MessageController@send');
|
||||
Route::post('messages', 'Api\Admin\MessageController@send');
|
||||
}
|
||||
|
||||
// Account creation token
|
||||
Route::post('account_creation_tokens', 'Api\Admin\AccountCreationTokenController@create');
|
||||
|
||||
// Accounts
|
||||
Route::get('accounts/{id}/activate', 'Api\Admin\AccountController@activate');
|
||||
Route::get('accounts/{id}/deactivate', 'Api\Admin\AccountController@deactivate');
|
||||
Route::get('accounts/{id}/provision', 'Api\Admin\AccountController@provision');
|
||||
|
||||
Route::post('accounts/{id}/recover-by-email', 'Api\Admin\AccountController@recoverByEmail');
|
||||
|
||||
Route::post('accounts', 'Api\Admin\AccountController@store');
|
||||
Route::put('accounts/{id}', 'Api\Admin\AccountController@update');
|
||||
Route::get('accounts', 'Api\Admin\AccountController@index');
|
||||
Route::get('accounts/{sip}/search', 'Api\Admin\AccountController@search');
|
||||
Route::get('accounts/{id}', 'Api\Admin\AccountController@show');
|
||||
Route::delete('accounts/{id}', 'Api\Admin\AccountController@destroy');
|
||||
Route::get('accounts/{sip}/search', 'Api\Admin\AccountController@search');
|
||||
Route::get('accounts/{email}/search-by-email', 'Api\Admin\AccountController@searchByEmail');
|
||||
|
||||
// Account actions
|
||||
Route::get('accounts/{id}/actions', 'Api\Admin\AccountActionController@index');
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ if (config('app.web_panel')) {
|
|||
Route::get('authenticate/qrcode/{token?}', 'Account\AuthenticateController@loginAuthToken')->name('account.authenticate.auth_token');
|
||||
}
|
||||
|
||||
Route::get('creation_token/check/{token}', 'Account\CreationRequestTokenController@check')->name('account.creation_request_token.check');
|
||||
Route::post('creation_token/validate', 'Account\CreationRequestTokenController@validateToken')->name('account.creation_request_token.validate');
|
||||
|
||||
Route::group(['middleware' => 'auth.digest_or_key'], function () {
|
||||
Route::get('provisioning/me', 'Account\ProvisioningController@me')->name('provisioning.me');
|
||||
|
||||
|
|
|
|||
|
|
@ -188,9 +188,9 @@ class AccountProvisioningTest extends TestCase
|
|||
->assertStatus(201)
|
||||
->assertJson([
|
||||
'token' => true
|
||||
])->content();
|
||||
]);
|
||||
|
||||
$authToken = json_decode($response)->token;
|
||||
$authToken = $response->json('token');
|
||||
|
||||
$password = Password::factory()->create();
|
||||
$password->account->generateApiKey();
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ class ApiAccountApiKeyTest extends TestCase
|
|||
->assertStatus(201)
|
||||
->assertJson([
|
||||
'token' => true
|
||||
])->content();
|
||||
]);
|
||||
|
||||
$authToken = json_decode($response)->token;
|
||||
$authToken = $response->json('token');
|
||||
|
||||
// Try to retrieve an API key from the un-attached auth_token
|
||||
$response = $this->json($this->method, $this->route . '/' . $authToken)
|
||||
|
|
@ -95,9 +95,9 @@ class ApiAccountApiKeyTest extends TestCase
|
|||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'api_key' => true
|
||||
])->content();
|
||||
]);
|
||||
|
||||
$apiKey = json_decode($response)->api_key;
|
||||
$apiKey = $response->json('api_key');
|
||||
|
||||
// Re-retrieve
|
||||
$this->json($this->method, $this->route . '/' . $authToken)
|
||||
|
|
@ -106,8 +106,7 @@ class ApiAccountApiKeyTest extends TestCase
|
|||
// Check the if the API key can be used for the account
|
||||
$response = $this->withHeaders(['x-api-key' => $apiKey])
|
||||
->json($this->method, '/api/accounts/me')
|
||||
->assertStatus(200)
|
||||
->content();
|
||||
->assertStatus(200);
|
||||
|
||||
// Try with a wrong From
|
||||
$response = $this->withHeaders([
|
||||
|
|
@ -115,10 +114,9 @@ class ApiAccountApiKeyTest extends TestCase
|
|||
'From' => 'sip:baduser@server.tld'
|
||||
])
|
||||
->json($this->method, '/api/accounts/me')
|
||||
->assertStatus(200)
|
||||
->content();
|
||||
->assertStatus(200);
|
||||
|
||||
// Check if the account was correctly attached
|
||||
$this->assertEquals(json_decode($response)->email, $password->account->email);
|
||||
$this->assertEquals($response->json('email'), $password->account->email);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,23 @@
|
|||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\AccountCreationRequestToken;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
use App\AccountCreationToken;
|
||||
use App\Admin;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ApiAccountCreationTokenTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected $tokenRoute = '/api/account_creation_tokens/send-by-push';
|
||||
protected $tokenRequestRoute = '/api/account_creation_request_tokens';
|
||||
protected $tokenUsingCreationTokenRoute = '/api/account_creation_tokens/using-account-creation-request-token';
|
||||
protected $accountRoute = '/api/accounts/with-account-creation-token';
|
||||
protected $adminRoute = '/api/account_creation_tokens';
|
||||
protected $method = 'POST';
|
||||
|
||||
protected $pnProvider = 'provider';
|
||||
|
|
@ -52,51 +58,24 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
$response->assertStatus(503);
|
||||
}
|
||||
|
||||
/**
|
||||
* For retro-compatibility only
|
||||
*/
|
||||
public function testRetrocopatibilityToken()
|
||||
public function testAdminEndpoint()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
$admin = Admin::factory()->create();
|
||||
$admin->account->generateApiKey();
|
||||
|
||||
$response = $this->json($this->method, '/api/tokens', [
|
||||
'pn_provider' => $token->pn_provider,
|
||||
'pn_param' => $token->pn_param,
|
||||
'pn_prid' => $token->pn_prid
|
||||
$response = $this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->adminRoute)
|
||||
->assertStatus(201);
|
||||
|
||||
$this->assertDatabaseHas('account_creation_tokens', [
|
||||
'token' => $response->json()['token']
|
||||
]);
|
||||
$response->assertStatus(503);
|
||||
}
|
||||
|
||||
public function testInvalidToken()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
// Valid token
|
||||
$response = $this->json($this->method, '/api/accounts/with-token', [
|
||||
'username' => 'username',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'token' => $token->token
|
||||
]);
|
||||
$response->assertStatus(200);
|
||||
|
||||
// Expired token
|
||||
$response = $this->json($this->method, '/api/accounts/with-token', [
|
||||
'username' => 'username2',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'token' => $token->token
|
||||
]);
|
||||
$response->assertStatus(422);
|
||||
}
|
||||
|
||||
/**
|
||||
* For retrocompatibility only
|
||||
*/
|
||||
public function testRetrocompatibilityInvalidToken()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
// Invalid token
|
||||
$response = $this->json($this->method, $this->accountRoute, [
|
||||
'username' => 'username',
|
||||
|
|
@ -125,9 +104,6 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
$response->assertStatus(422);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test username blacklist
|
||||
*/
|
||||
public function testBlacklistedUsername()
|
||||
{
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
|
@ -165,4 +141,36 @@ class ApiAccountCreationTokenTest extends TestCase
|
|||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testAccountCreationRequestToken()
|
||||
{
|
||||
$response = $this->json($this->method, $this->tokenRequestRoute);
|
||||
$response->assertStatus(201);
|
||||
$creationRequestToken = $response->json()['token'];
|
||||
|
||||
$this->assertSame($response->json()['validation_url'], route('account.creation_request_token.check', $creationRequestToken));
|
||||
|
||||
// Validate the creation request token
|
||||
AccountCreationRequestToken::where('token', $creationRequestToken)->update(['validated_at' => Carbon::now()]);
|
||||
|
||||
$response = $this->json($this->method, $this->tokenUsingCreationTokenRoute, [
|
||||
'account_creation_request_token' => $creationRequestToken
|
||||
])->assertStatus(201);
|
||||
|
||||
$creationToken = $response->json()['token'];
|
||||
|
||||
$this->assertDatabaseHas('account_creation_request_tokens', [
|
||||
'token' => $creationRequestToken,
|
||||
'used' => true
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('account_creation_tokens', [
|
||||
'token' => $creationToken
|
||||
]);
|
||||
|
||||
$this->assertSame(
|
||||
AccountCreationRequestToken::where('token', $creationRequestToken)->first()->accountCreationToken->id,
|
||||
AccountCreationToken::where('token', $creationToken)->first()->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Tests\Feature;
|
|||
|
||||
use App\Password;
|
||||
use App\Account;
|
||||
use App\AccountCreationToken;
|
||||
use App\AccountTombstone;
|
||||
use App\ActivationExpiration;
|
||||
use App\Admin;
|
||||
|
|
@ -49,7 +50,7 @@ class ApiAccountTest extends TestCase
|
|||
$password = Password::factory()->create();
|
||||
$response0 = $this->generateFirstResponse($password);
|
||||
$response1 = $this->generateSecondResponse($password, $response0)
|
||||
->json($this->method, $this->route);
|
||||
->json($this->method, $this->route);
|
||||
|
||||
$response1->assertStatus(403);
|
||||
}
|
||||
|
|
@ -90,12 +91,12 @@ class ApiAccountTest extends TestCase
|
|||
$domain = 'example.com';
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
]);
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
]);
|
||||
|
||||
$response->assertJsonValidationErrors(['username']);
|
||||
}
|
||||
|
|
@ -110,28 +111,35 @@ class ApiAccountTest extends TestCase
|
|||
$username = 'blabla🔥';
|
||||
$domain = 'example.com';
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
]);
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertJsonValidationErrors(['username']);
|
||||
|
||||
$response->assertJsonValidationErrors(['username']);
|
||||
// Change the regex
|
||||
config()->set('app.account_username_regex', '^[a-z0-9🔥+_.-]*$');
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertStatus(200);
|
||||
|
||||
$username = 'blabla hop';
|
||||
$domain = 'example.com';
|
||||
|
||||
$response = $this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
]);
|
||||
|
||||
$response->assertJsonValidationErrors(['username']);
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'domain' => $domain,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '123456',
|
||||
])->assertJsonValidationErrors(['username']);
|
||||
}
|
||||
|
||||
public function testDomain()
|
||||
|
|
@ -318,7 +326,7 @@ class ApiAccountTest extends TestCase
|
|||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'password' => 'blabla',
|
||||
'admin' => true,
|
||||
]);
|
||||
|
||||
|
|
@ -348,7 +356,7 @@ class ApiAccountTest extends TestCase
|
|||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'password' => 'blabla',
|
||||
'activated' => true,
|
||||
]);
|
||||
|
||||
|
|
@ -378,7 +386,7 @@ class ApiAccountTest extends TestCase
|
|||
->json($this->method, $this->route, [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'password' => 'blabla',
|
||||
'activated' => false,
|
||||
]);
|
||||
|
||||
|
|
@ -407,7 +415,7 @@ class ApiAccountTest extends TestCase
|
|||
/**
|
||||
* Public information
|
||||
*/
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/info')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false,
|
||||
|
|
@ -421,7 +429,7 @@ class ApiAccountTest extends TestCase
|
|||
* Retrieve the authenticated account
|
||||
*/
|
||||
$this->keyAuthenticated($password->account)
|
||||
->get($this->route.'/me')
|
||||
->get($this->route . '/me')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $password->account->username,
|
||||
|
|
@ -433,14 +441,14 @@ class ApiAccountTest extends TestCase
|
|||
* Retrieve the authenticated account
|
||||
*/
|
||||
$this->keyAuthenticated($password->account)
|
||||
->delete($this->route.'/me')
|
||||
->delete($this->route . '/me')
|
||||
->assertStatus(200);
|
||||
|
||||
/**
|
||||
* Check again
|
||||
*/
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
->assertStatus(404);
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/info')
|
||||
->assertStatus(404);
|
||||
}
|
||||
|
||||
public function testActivateEmail()
|
||||
|
|
@ -457,36 +465,36 @@ class ApiAccountTest extends TestCase
|
|||
$expiration->expires = Carbon::now()->subYear();
|
||||
$expiration->save();
|
||||
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/info')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/blabla/activate/email', [
|
||||
'code' => $confirmationKey
|
||||
->json($this->method, $this->route . '/blabla/activate/email', [
|
||||
'confirmation_key' => $confirmationKey
|
||||
])
|
||||
->assertStatus(404);
|
||||
|
||||
$activateEmailRoute = $this->route.'/'.$password->account->identifier.'/activate/email';
|
||||
$activateEmailRoute = $this->route . '/' . $password->account->identifier . '/activate/email';
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $activateEmailRoute, [
|
||||
'code' => $confirmationKey.'longer'
|
||||
'confirmation_key' => $confirmationKey . 'longer'
|
||||
])
|
||||
->assertStatus(422);
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $activateEmailRoute, [
|
||||
'code' => 'X123456789abc'
|
||||
'confirmation_key' => 'X123456789abc'
|
||||
])
|
||||
->assertStatus(404);
|
||||
|
||||
// Expired
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $activateEmailRoute, [
|
||||
'code' => $confirmationKey
|
||||
'confirmation_key' => $confirmationKey
|
||||
])
|
||||
->assertStatus(403);
|
||||
|
||||
|
|
@ -494,11 +502,11 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $activateEmailRoute, [
|
||||
'code' => $confirmationKey
|
||||
'confirmation_key' => $confirmationKey
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/info')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true
|
||||
|
|
@ -532,6 +540,49 @@ class ApiAccountTest extends TestCase
|
|||
->assertJsonValidationErrors(['email']);
|
||||
}
|
||||
|
||||
public function testEditAdmin()
|
||||
{
|
||||
$password = Password::factory()->create();
|
||||
$account = $password->account;
|
||||
|
||||
$admin = Admin::factory()->create();
|
||||
$admin->account->generateApiKey();
|
||||
$admin->account->save();
|
||||
|
||||
$username = 'changed';
|
||||
$algorithm = 'MD5';
|
||||
$password = 'other';
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json('PUT', $this->route. '/1234')
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['username']);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json('PUT', $this->route. '/1234', [
|
||||
'username' => 'good'
|
||||
])
|
||||
->assertStatus(422);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json('PUT', $this->route. '/'. $account->id, [
|
||||
'username' => $username,
|
||||
'algorithm' => $algorithm,
|
||||
'password' => $password,
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'id' => $account->id,
|
||||
'username' => $username
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('passwords', [
|
||||
'account_id' => $account->id,
|
||||
'algorithm' => $algorithm
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* /!\ Dangerous endpoints
|
||||
*/
|
||||
|
|
@ -552,21 +603,44 @@ class ApiAccountTest extends TestCase
|
|||
'activated' => false
|
||||
]);
|
||||
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/recover/'.$confirmationKey)
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/recover/' . $confirmationKey)
|
||||
->assertJson(['passwords' => [[
|
||||
'password' => $password->password,
|
||||
'algorithm' => $password->algorithm
|
||||
]]])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->json('GET', $this->route.'/'.$password->account->identifier.'/recover/'.$confirmationKey)
|
||||
$this->json('GET', $this->route . '/' . $password->account->identifier . '/recover/' . $confirmationKey)
|
||||
->assertStatus(404);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'username' => $password->account->username,
|
||||
'domain' => $password->account->domain,
|
||||
'confirmation_key' => null,
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
// Recover by alias
|
||||
|
||||
$newConfirmationKey = '1345';
|
||||
|
||||
$password->account->confirmation_key = $newConfirmationKey;
|
||||
$password->account->save();
|
||||
|
||||
$phone = '+1234';
|
||||
|
||||
$alias = new AppAlias;
|
||||
$alias->alias = $phone;
|
||||
$alias->domain = $password->account->domain;
|
||||
$alias->account_id = $password->account->id;
|
||||
$alias->save();
|
||||
|
||||
$this->get($this->route . '/' . $phone . '@' . $alias->domain . '/recover/' . $newConfirmationKey)
|
||||
->assertJson(['passwords' => [[
|
||||
'password' => $password->password,
|
||||
'algorithm' => $password->algorithm
|
||||
]]])
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -589,36 +663,67 @@ class ApiAccountTest extends TestCase
|
|||
$alias->account_id = $password->account->id;
|
||||
$alias->save();
|
||||
|
||||
$this->json($this->method, $this->route.'/recover-by-phone', [
|
||||
'phone' => $phone
|
||||
])
|
||||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone
|
||||
])
|
||||
->assertStatus(422)
|
||||
->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']);
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
$this->json($this->method, $this->route . '/recover-by-phone', [
|
||||
'phone' => $phone,
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$password->account->refresh();
|
||||
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/recover/'.$password->account->confirmation_key)
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/recover/' . $password->account->confirmation_key)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->get($this->route.'/'.$phone.'/info-by-phone')
|
||||
$this->get($this->route . '/' . $phone . '/info-by-phone')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true
|
||||
'activated' => true,
|
||||
'phone' => true
|
||||
]);
|
||||
|
||||
$this->get($this->route.'/+1234/info-by-phone')
|
||||
$this->get($this->route . '/+1234/info-by-phone')
|
||||
->assertStatus(404);
|
||||
|
||||
$this->json('GET', $this->route.'/'.$password->account->identifier.'/info-by-phone')
|
||||
$this->json('GET', $this->route . '/' . $password->account->identifier . '/info-by-phone')
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
|
||||
// Check the mixed username/phone resolution...
|
||||
$password->account->username = $phone;
|
||||
$password->account->save();
|
||||
|
||||
$alias->delete();
|
||||
|
||||
$this->get($this->route . '/' . $phone . '/info-by-phone')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true,
|
||||
'phone' => false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* /!\ Dangerous endpoints
|
||||
*/
|
||||
|
||||
public function testCreatePublic()
|
||||
{
|
||||
$username = 'publicuser';
|
||||
|
|
@ -626,50 +731,66 @@ class ApiAccountTest extends TestCase
|
|||
config()->set('app.dangerous_endpoints', true);
|
||||
|
||||
// Missing email
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['account_creation_token']);
|
||||
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
$userAgent = 'User Agent Test';
|
||||
|
||||
$this->withHeaders([
|
||||
'User-Agent' => $userAgent,
|
||||
])->json($this->method, $this->route . '/public', [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
|
||||
// Already created
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'username' => $username,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['username']);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['username']);
|
||||
|
||||
// Email is now unique
|
||||
config()->set('app.account_email_unique', true);
|
||||
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'username' => 'johndoe',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['email']);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'username' => $username,
|
||||
'domain' => config('app.sip_domain')
|
||||
'domain' => config('app.sip_domain'),
|
||||
'user_agent' => $userAgent
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -679,47 +800,39 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
config()->set('app.dangerous_endpoints', true);
|
||||
|
||||
// Username and phone
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
'username' => 'myusername',
|
||||
'phone' => $phone,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone', 'username']);
|
||||
|
||||
// Bad phone format
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'phone' => 'username',
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$token = AccountCreationToken::factory()->create();
|
||||
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'phone' => $phone,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
'account_creation_token' => $token->token
|
||||
])
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
|
||||
// Already exists
|
||||
$this->json($this->method, $this->route.'/public', [
|
||||
$this->json($this->method, $this->route . '/public', [
|
||||
'phone' => $phone,
|
||||
'algorithm' => 'SHA-256',
|
||||
'password' => '2',
|
||||
'email' => 'john@doe.tld',
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors(['phone']);
|
||||
|
||||
$this->assertDatabaseHas('accounts', [
|
||||
'username' => $phone,
|
||||
|
|
@ -746,7 +859,7 @@ class ApiAccountTest extends TestCase
|
|||
$expiration->expires = Carbon::now()->subYear();
|
||||
$expiration->save();
|
||||
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
$this->get($this->route . '/' . $password->account->identifier . '/info')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
|
|
@ -754,16 +867,16 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Expired
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/'.$password->account->identifier.'/activate/phone', [
|
||||
'code' => $confirmationKey
|
||||
->json($this->method, $this->route . '/' . $password->account->identifier . '/activate/phone', [
|
||||
'confirmation_key' => $confirmationKey
|
||||
])
|
||||
->assertStatus(403);
|
||||
|
||||
$expiration->delete();
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/'.$password->account->identifier.'/activate/phone', [
|
||||
'code' => $confirmationKey
|
||||
->json($this->method, $this->route . '/' . $password->account->identifier . '/activate/phone', [
|
||||
'confirmation_key' => $confirmationKey
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
|
|
@ -783,27 +896,27 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Bad email
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/me/email/request', [
|
||||
->json($this->method, $this->route . '/me/email/request', [
|
||||
'email' => 'gnap'
|
||||
])
|
||||
->assertStatus(422);
|
||||
|
||||
// Same email
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/me/email/request', [
|
||||
->json($this->method, $this->route . '/me/email/request', [
|
||||
'email' => $password->account->email
|
||||
])
|
||||
->assertStatus(422);
|
||||
|
||||
// Correct email
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/me/email/request', [
|
||||
->json($this->method, $this->route . '/me/email/request', [
|
||||
'email' => $newEmail
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->get($this->route.'/me')
|
||||
->get($this->route . '/me')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $password->account->username,
|
||||
|
|
@ -816,7 +929,7 @@ class ApiAccountTest extends TestCase
|
|||
config()->set('app.account_email_unique', true);
|
||||
|
||||
$this->keyAuthenticated($password->account)
|
||||
->json($this->method, $this->route.'/me/email/request', [
|
||||
->json($this->method, $this->route . '/me/email/request', [
|
||||
'email' => $otherAccount->account->email
|
||||
])
|
||||
->assertStatus(422)
|
||||
|
|
@ -834,7 +947,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Wrong algorithm
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route.'/me/password', [
|
||||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => '123',
|
||||
'password' => $password
|
||||
])
|
||||
|
|
@ -843,7 +956,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Fresh password without an old one
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route.'/me/password', [
|
||||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => $algorithm,
|
||||
'password' => $password
|
||||
])
|
||||
|
|
@ -851,7 +964,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// First check
|
||||
$this->keyAuthenticated($account)
|
||||
->get($this->route.'/me')
|
||||
->get($this->route . '/me')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $account->username,
|
||||
|
|
@ -862,7 +975,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Set new password without old one
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route.'/me/password', [
|
||||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => $newAlgorithm,
|
||||
'password' => $newPassword
|
||||
])
|
||||
|
|
@ -871,7 +984,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Set the new password with incorrect old password
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route.'/me/password', [
|
||||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => $newAlgorithm,
|
||||
'old_password' => 'blabla',
|
||||
'password' => $newPassword
|
||||
|
|
@ -881,7 +994,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Set the new password
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route.'/me/password', [
|
||||
->json($this->method, $this->route . '/me/password', [
|
||||
'algorithm' => $newAlgorithm,
|
||||
'old_password' => $password,
|
||||
'password' => $newPassword
|
||||
|
|
@ -890,7 +1003,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Second check
|
||||
$this->keyAuthenticated($account)
|
||||
->get($this->route.'/me')
|
||||
->get($this->route . '/me')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $account->username,
|
||||
|
|
@ -909,28 +1022,28 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// deactivate
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->id.'/deactivate')
|
||||
->get($this->route . '/' . $password->account->id . '/deactivate')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->id)
|
||||
->get($this->route . '/' . $password->account->id)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->id.'/activate')
|
||||
->get($this->route . '/' . $password->account->id . '/activate')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->id)
|
||||
->get($this->route . '/' . $password->account->id)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => true
|
||||
|
|
@ -938,12 +1051,44 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// Search feature
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->identifier.'/search')
|
||||
->get($this->route . '/' . $password->account->identifier . '/search')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'id' => $password->account->id,
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route . '/' . $password->account->email . '/search-by-email')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'id' => $password->account->id,
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route . '/wrong@email.com/search-by-email')
|
||||
->assertStatus(404);
|
||||
}
|
||||
|
||||
public function testRecoverByEmail()
|
||||
{
|
||||
$email = 'collision@email.com';
|
||||
|
||||
$account = Password::factory()->create();
|
||||
$account->account->email = $email;
|
||||
$account->account->save();
|
||||
|
||||
$admin = Admin::factory()->create();
|
||||
$admin->account->generateApiKey();
|
||||
$admin->account->save();
|
||||
|
||||
$response = $this->keyAuthenticated($admin->account)
|
||||
->post($this->route . '/' . $account->id . '/recover-by-email')
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertNotEquals($response->json('confirmation_key'), $account->confirmation_key);
|
||||
$this->assertNotEquals($response->json('provisioning_token'), $account->provisioning_token);
|
||||
}
|
||||
|
||||
public function testGetAll()
|
||||
|
|
@ -963,7 +1108,7 @@ class ApiAccountTest extends TestCase
|
|||
|
||||
// /accounts/id
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$admin->id)
|
||||
->get($this->route . '/' . $admin->id)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'id' => 1,
|
||||
|
|
@ -1024,13 +1169,13 @@ class ApiAccountTest extends TestCase
|
|||
$admin->account->generateApiKey();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete($this->route.'/'.$password->account->id)
|
||||
->delete($this->route . '/' . $password->account->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(1, AccountTombstone::count());
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password->account->id)
|
||||
->get($this->route . '/' . $password->account->id)
|
||||
->assertStatus(404);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,17 +47,26 @@ class ApiAccountTypeTest extends TestCase
|
|||
|
||||
$this->assertEquals(1, AccountType::count());
|
||||
|
||||
// Same key
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route, [
|
||||
'key' => 'phone',
|
||||
])
|
||||
->assertJsonValidationErrorFor('key')
|
||||
->assertStatus(422);
|
||||
|
||||
|
||||
// Missing key
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route, [])
|
||||
->assertStatus(422);
|
||||
->json($this->method, $this->route, [])
|
||||
->assertStatus(422);
|
||||
|
||||
// Invalid key
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route, [
|
||||
'key' => 'Abc1234',
|
||||
])
|
||||
->assertStatus(422);
|
||||
->json($this->method, $this->route, [
|
||||
'key' => 'Abc1234',
|
||||
])
|
||||
->assertStatus(422);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route)
|
||||
|
|
@ -83,7 +92,7 @@ class ApiAccountTypeTest extends TestCase
|
|||
$accountType = AccountType::first();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete($this->route.'/'.$accountType->id)
|
||||
->delete($this->route . '/' . $accountType->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(0, AccountType::count());
|
||||
|
|
@ -104,7 +113,7 @@ class ApiAccountTypeTest extends TestCase
|
|||
$accountType = AccountType::first();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json('PUT', $this->route.'/'.$accountType->id, [
|
||||
->json('PUT', $this->route . '/' . $accountType->id, [
|
||||
'key' => 'door',
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
|
@ -137,34 +146,34 @@ class ApiAccountTypeTest extends TestCase
|
|||
$password = Password::factory()->create();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, '/api/accounts/'.$password->account->id.'/types/'.$accountType->id)
|
||||
->json($this->method, '/api/accounts/' . $password->account->id . '/types/' . $accountType->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, '/api/accounts/'.$password->account->id.'/types/'.$accountType->id)
|
||||
->json($this->method, '/api/accounts/' . $password->account->id . '/types/' . $accountType->id)
|
||||
->assertStatus(403);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get('/api/accounts/'.$password->account->id)
|
||||
->assertJson([
|
||||
'types' => [
|
||||
[
|
||||
'id' => $accountType->id,
|
||||
'key' => $accountType->key
|
||||
->get('/api/accounts/' . $password->account->id)
|
||||
->assertJson([
|
||||
'types' => [
|
||||
[
|
||||
'id' => $accountType->id,
|
||||
'key' => $accountType->key
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
]);
|
||||
|
||||
// Remove
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete('/api/accounts/'.$password->account->id.'/types/'.$accountType->id)
|
||||
->delete('/api/accounts/' . $password->account->id . '/types/' . $accountType->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(0, DB::table('account_account_type')->count());
|
||||
|
||||
// Retry
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete('/api/accounts/'.$password->account->id.'/types/'.$accountType->id)
|
||||
->delete('/api/accounts/' . $password->account->id . '/types/' . $accountType->id)
|
||||
->assertStatus(403);
|
||||
$this->assertEquals(0, DB::table('account_account_type')->count());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue