diff --git a/flexiapi/.env.example b/flexiapi/.env.example index ea5bde0..dbf1138 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -5,6 +5,7 @@ APP_DEBUG=false APP_URL=http://localhost APP_SIP_DOMAIN=sip.example.com APP_FLEXISIP_PROXY_PID=/var/run/flexisip-proxy.pid +APP_FLEXISIP_PUSHER_PATH= APP_EVERYONE_IS_ADMIN=false # SIP server parameters diff --git a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php index 0894e13..6d7a080 100644 --- a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php +++ b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php @@ -59,7 +59,7 @@ class AuthenticateController extends Controller foreach ($account->passwords as $password) { if (hash_equals( $password->password, - Utils::bchash($request->get('username'), $account->domain, $request->get('password'), $password->algorithm) + Utils::bchash($request->get('username'), $account->resolvedRealm, $request->get('password'), $password->algorithm) )) { Auth::login($account); return redirect()->route('account.panel'); diff --git a/flexiapi/app/Http/Controllers/Api/AccountController.php b/flexiapi/app/Http/Controllers/Api/AccountController.php index a77860e..c60cbc2 100644 --- a/flexiapi/app/Http/Controllers/Api/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/AccountController.php @@ -20,9 +20,14 @@ namespace App\Http\Controllers\Api; use Illuminate\Http\Request; +use Illuminate\Validation\Rule; +use Illuminate\Support\Str; use App\Http\Controllers\Controller; -use App\Account; +use App\Rules\WithoutSpaces; +use Carbon\Carbon; +use App\Account; +use App\Token; use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; class AccountController extends Controller @@ -40,6 +45,52 @@ class AccountController extends Controller ]); } + public function store(Request $request) + { + $request->validate([ + 'username' => [ + 'required', + Rule::unique('external.accounts', 'username')->where(function ($query) use ($request) { + $query->where('domain', config('app.sip_domain')); + }), + 'filled', + new WithoutSpaces + ], + 'algorithm' => 'required|in:SHA-256,MD5', + 'password' => 'required|filled', + 'domain' => 'min:3', + 'token' => [ + 'required', + Rule::exists('tokens', 'token')->where(function ($query) { + $query->where('used', false); + }), + 'size:'.WebAuthenticateController::$emailCodeSize + ] + ]); + + $token = Token::where('token', $request->get('token'))->first(); + $token->used = true; + $token->save(); + + $account = new Account; + $account->username = $request->get('username'); + $account->email = $request->get('email'); + $account->activated = false; + $account->domain = $request->has('domain') + ? $request->get('domain') + : config('app.sip_domain'); + $account->ip_address = $request->ip(); + $account->creation_time = Carbon::now(); + $account->user_agent = config('app.name'); + $account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize); + $account->save(); + + $account->updatePassword($request->get('password'), $request->get('algorithm')); + + // Full reload + return Account::withoutGlobalScopes()->find($account->id); + } + public function activateEmail(Request $request, string $sip) { $request->validate([ diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php index 8b1cf92..ba80b50 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php @@ -105,11 +105,7 @@ class AccountController extends Controller $account->save(); - $password = new Password; - $password->account_id = $account->id; - $password->password = Utils::bchash($account->username, $account->resolvedRealm, $request->get('password'), $request->get('algorithm')); - $password->algorithm = $request->get('algorithm'); - $password->save(); + $account->updatePassword($request->get('password'), $request->get('algorithm')); if ($request->has('admin') && (bool)$request->get('admin')) { $admin = new Admin; diff --git a/flexiapi/app/Http/Controllers/Api/TokenController.php b/flexiapi/app/Http/Controllers/Api/TokenController.php new file mode 100644 index 0000000..74842de --- /dev/null +++ b/flexiapi/app/Http/Controllers/Api/TokenController.php @@ -0,0 +1,52 @@ +validate([ + 'pn_provider' => 'required', + 'pn_param' => 'required', + 'pn_prid' => 'required', + ]); + + if (Token::where('pn_provider', $request->get('pn_provider')) + ->where('pn_param', $request->get('pn_param')) + ->where('pn_prid', $request->get('pn_prid')) + ->where('used', false) + ->count() > 0) { + abort(403, 'A similar token was already used'); + } + + if (Token::where('pn_provider', $request->get('pn_provider')) + ->where('pn_param', $request->get('pn_param')) + ->where('pn_prid', $request->get('pn_prid')) + ->count() > 3) { + abort(403, 'The limit of tokens generated for this device has been reached'); + } + + $token = new Token; + $token->token = Str::random(WebAuthenticateController::$emailCodeSize); + $token->pn_provider = $request->get('pn_provider'); + $token->pn_param = $request->get('pn_param'); + $token->pn_prid = $request->get('pn_prid'); + + // Send the token to the device via Push Notification + $fp = new FlexisipPusherConnector($token->pn_provider, $token->pn_param, $token->pn_prid); + if ($fp->sendToken($token->token)) { + $token->save(); + } else { + abort(503, "Token not sent"); + } + } +} diff --git a/flexiapi/app/Libraries/FlexisipConnector.php b/flexiapi/app/Libraries/FlexisipConnector.php index 4c6374e..0e4887c 100644 --- a/flexiapi/app/Libraries/FlexisipConnector.php +++ b/flexiapi/app/Libraries/FlexisipConnector.php @@ -56,7 +56,7 @@ class FlexisipConnector public function deleteDevice(string $from, string $uuid) { - $content = $this->request('REGISTRAR_DELETE', [ + $this->request('REGISTRAR_DELETE', [ 'sip:'.$from, '"<'.$uuid.'>"', ]); diff --git a/flexiapi/app/Libraries/FlexisipPusherConnector.php b/flexiapi/app/Libraries/FlexisipPusherConnector.php new file mode 100644 index 0000000..595756a --- /dev/null +++ b/flexiapi/app/Libraries/FlexisipPusherConnector.php @@ -0,0 +1,52 @@ +. +*/ + +namespace App\Libraries; + +class FlexisipPusherConnector +{ + private $pusherPath; + private $pnProvider; + private $pnParam; + private $pnPrid; + + public function __construct(string $pnProvider, string $pnParam, string $pnPrid) + { + $this->pusherPath = config('app.flexisip_pusher_path'); + $this->pnProvider = $pnProvider; + $this->pnParam = $pnParam; + $this->pnPrid = $pnPrid; + } + + public function sendToken(string $token) + { + $payload = json_encode(['token' => $token]); + + $command = $this->pusherPath + . " --pn-provider " . $this->pnProvider + . " --pn-param " . $this->pnParam + . " --pn-prid " . $this->pnPrid + . " --apple-push-type Background --customPayload " . $payload; + + $output = null; + $retval = null; + + return exec($command, $output, $retval); + } +} \ No newline at end of file diff --git a/flexiapi/app/Token.php b/flexiapi/app/Token.php new file mode 100644 index 0000000..b37fecc --- /dev/null +++ b/flexiapi/app/Token.php @@ -0,0 +1,11 @@ + env('APP_NAME', 'Laravel'), 'sip_domain' => env('APP_SIP_DOMAIN', 'sip.domain.com'), 'flexisip_proxy_pid' => env('APP_FLEXISIP_PROXY_PID', '/var/run/flexisip-proxy.pid'), + 'flexisip_pusher_path' => env('APP_FLEXISIP_PUSHER_PATH', ''), 'terms_of_use_url' => env('TERMS_OF_USE_URL', ''), 'privacy_policy_url' => env('PRIVACY_POLICY_URL', ''), diff --git a/flexiapi/database/factories/TokenFactory.php b/flexiapi/database/factories/TokenFactory.php new file mode 100644 index 0000000..c1745cb --- /dev/null +++ b/flexiapi/database/factories/TokenFactory.php @@ -0,0 +1,42 @@ +. +*/ + +namespace Database\Factories; + +use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Str; + +use App\Token; +use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; + +class TokenFactory extends Factory +{ + protected $model = Token::class; + + public function definition() + { + return [ + 'pn_provider' => $this->faker->uuid, + 'pn_param' => $this->faker->uuid, + 'pn_prid' => $this->faker->uuid, + 'token' => Str::random(WebAuthenticateController::$emailCodeSize), + 'used' => false + ]; + } +} diff --git a/flexiapi/database/migrations/2021_02_23_152536_create_tokens_table.php b/flexiapi/database/migrations/2021_02_23_152536_create_tokens_table.php new file mode 100644 index 0000000..09e0586 --- /dev/null +++ b/flexiapi/database/migrations/2021_02_23_152536_create_tokens_table.php @@ -0,0 +1,29 @@ +create('tokens', function (Blueprint $table) { + $table->id(); + $table->string('token'); + $table->string('pn_provider'); + $table->string('pn_param'); + $table->string('pn_prid'); + $table->boolean('used')->default(false); + $table->timestamps(); + + $table->index('token'); + $table->index(['pn_provider', 'pn_param', 'pn_prid']); + }); + } + + public function down() + { + Schema::connection('local')->dropIfExists('tokens'); + } +} diff --git a/flexiapi/resources/views/documentation.blade.php b/flexiapi/resources/views/documentation.blade.php index 42290af..12cad9e 100644 --- a/flexiapi/resources/views/documentation.blade.php +++ b/flexiapi/resources/views/documentation.blade.php @@ -55,6 +55,31 @@ For the moment only DIGEST-MD5 and DIGEST-SHA-256 are supported through the auth
POST /tokensSend a token using a push notification to the device.
+Return 403 if a token was already sent, or if the tokens limit is reached for this device.
Return 503 if the token was not successfully sent.
JSON parameters:
+pn_provider the push notification providerpn_param the push notification parameterpn_prid the push notification unique idPOST /accounts/with-tokenCreate an account using a token.
+Return 422 if the parapeters are invalid or if the token is expired.
JSON parameters:
+username unique username, minimum 6 characterspassword required minimum 6 charactersalgorithm required, values can be SHA-256 or MD5domain optional, the value is set to the default registration domain if not settoken the unique tokenGET /accounts/{sip}/infoRetrieve public information about the account.
Return 404 if the account doesn't exists.