Store hashed tokens (bearer and api) instead of clear
This commit is contained in:
parent
53e76d86e6
commit
20935014c4
12 changed files with 102 additions and 81 deletions
|
|
@ -29,10 +29,8 @@ class ApiController extends Controller
|
|||
throw new ApiErrorException(ApiError::authFailed());
|
||||
}
|
||||
|
||||
$service->generateApiToken();
|
||||
$apiToken = $service->generateApiToken();
|
||||
$service->saveOrFail();
|
||||
return response()->json([
|
||||
'api_token' => $service->api_token
|
||||
]);
|
||||
return response()->json(['api_token' => $apiToken]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ class ServiceController extends Controller
|
|||
|
||||
public function generateApiToken(Service $service)
|
||||
{
|
||||
$service->generateBearerToken();
|
||||
$bearerPwd = $service->bearer_pwd;
|
||||
$bearerToken = $service->generateBearerToken();
|
||||
$bearerPwd = $service->bearer_pwd;
|
||||
$service->saveOrFail();
|
||||
return back()
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
->with('success', "Nouveau token d'API de $service->name : '$service->bearer_token' et mot de passe '$bearerPwd'");
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
->with('success', "Nouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,40 +28,6 @@ class Authenticate extends Middleware
|
|||
public function handle($request, Closure $next, ...$guards)
|
||||
{
|
||||
$isApiV1Request = $request->isJson() && Helper::isRequestForApiV1($request);
|
||||
$isApiV2Request = $request->isJson() && Helper::isRequestForApiV2($request);
|
||||
|
||||
if ($isApiV2Request) {
|
||||
// https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly/25843058
|
||||
/**
|
||||
* Un token devrais être fourni pour chaque requete apres authentification
|
||||
* api_token param est requis par Auth
|
||||
*/
|
||||
$apiToken = $request->api_token;
|
||||
|
||||
if (is_null($apiToken)) {
|
||||
$this->unauthenticated($request, $guards);
|
||||
}
|
||||
|
||||
/**
|
||||
** All ApiV2 routes must pass Brand model to controller action since a Brand owns
|
||||
** a service token, otherwise the api cannot be called.
|
||||
** This hack is a custom binder instead of passing
|
||||
** this to route as a parameter
|
||||
*/
|
||||
if (!($route = Helper::getRoute($request))) {
|
||||
$this->unauthenticated($request, $guards);
|
||||
}
|
||||
|
||||
try {
|
||||
$service = Service::getFromApiToken($apiToken);
|
||||
$service->authenticateApi($guards, $apiToken);
|
||||
app()->bind(Service::class, function () use ($service) {
|
||||
return $service;
|
||||
});
|
||||
} catch (ModelNotFoundException $e) {
|
||||
throw new AuthenticationException('Unauthenticated.', $guards);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check this works
|
||||
if ($isApiV1Request) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use App\Models\ServiceStat;
|
|||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class CheckBrand
|
||||
{
|
||||
|
|
@ -28,21 +29,22 @@ class CheckBrand
|
|||
} else {
|
||||
$this->apiToken = ($request->api_token !== null ? $request->api_token : null);
|
||||
}
|
||||
print_r($request->all());
|
||||
|
||||
if ($this->apiToken === null) {
|
||||
die("Le token d'authentification est incorrect");
|
||||
abort(401, 'Le token d\'authentification est incorrect');
|
||||
}
|
||||
$brand = Brand::whereHas('services', function (Builder $query) {
|
||||
$query->where("api_token", hash('sha256', $this->apiToken));
|
||||
$query->where("bearer_token", Hash::make($this->apiToken));
|
||||
})->first();
|
||||
|
||||
if (!$brand) {
|
||||
die("Le token d'authentification est incorrect");
|
||||
abort(401, 'Le token d\'authentification est incorrect');
|
||||
}
|
||||
|
||||
$serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]);
|
||||
$serviceStat->brand_id = $brand->id;
|
||||
$serviceStat->service_id = Service::where('api_token', hash('sha256', $this->apiToken))->first()->id;
|
||||
$serviceStat->service_id = Service::where('bearer_token', Hash::make($this->apiToken))->first()->id;
|
||||
$serviceStat->nb_calls = $serviceStat->nb_calls === 0 ? 1 : $serviceStat->nb_calls + 1;
|
||||
$serviceStat->save();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Lib\Helper;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
|
@ -16,7 +17,8 @@ class JsonOnly
|
|||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (!$request->isJson()) {
|
||||
// TODO reve v1 check when dropped
|
||||
if (!Helper::isRequestForApiV1($request) and !$request->isJson()) {
|
||||
abort(400);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
|
@ -19,16 +18,28 @@ class Service extends Model
|
|||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($service) {
|
||||
static::creating(function (self $service) {
|
||||
$service->generateBearerToken();
|
||||
if (Hash::needsRehash($service->bearer_token)) {
|
||||
$service->bearer_token = Hash::make($service->bearer_token);
|
||||
}
|
||||
if (Hash::needsRehash($service->bearer_pwd)) {
|
||||
$service->bearer_pwd = Hash::make($service->bearer_pwd);
|
||||
}
|
||||
if (Hash::needsRehash($service->api_token)) {
|
||||
$service->api_token = Hash::make($service->api_token);
|
||||
}
|
||||
});
|
||||
static::updating(function ($service) {
|
||||
static::updating(function (self $service) {
|
||||
if (Hash::needsRehash($service->bearer_token)) {
|
||||
$service->bearer_token = Hash::make($service->bearer_token);
|
||||
}
|
||||
if (Hash::needsRehash($service->bearer_pwd)) {
|
||||
$service->bearer_pwd = Hash::make($service->bearer_pwd);
|
||||
}
|
||||
if (Hash::needsRehash($service->api_token)) {
|
||||
$service->api_token = Hash::make($service->api_token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -75,12 +86,18 @@ class Service extends Model
|
|||
* Get Service from apiToken with its brand
|
||||
*
|
||||
* @param string $apiToken
|
||||
* @return \App\Models\Service|null
|
||||
* @return \App\Models\Service
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public static function getFromApiToken(string $apiToken)
|
||||
public static function getFromApiToken(string $apiToken): self
|
||||
{
|
||||
return static::where('api_token', $apiToken)->with('brand')->firstOrFail();
|
||||
$services = static::all();
|
||||
foreach ($services as $service) {
|
||||
if (Hash::check($apiToken, $service->api_token)) {
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
throw new ModelNotFoundException('Could not find service from Api Token');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,9 +107,15 @@ class Service extends Model
|
|||
* @return \App\Models\Service
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public static function getFromBearerToken(string $bearerToken)
|
||||
public static function getFromBearerToken(string $bearerToken): self
|
||||
{
|
||||
return static::where('bearer_token', $bearerToken)->with('brand')->firstOrFail();
|
||||
$services = static::all();
|
||||
foreach ($services as $service) {
|
||||
if (Hash::check($bearerToken, $service->bearer_token)) {
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
throw new ModelNotFoundException('Could not find service from Service Token');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,17 +123,12 @@ class Service extends Model
|
|||
*
|
||||
* @param array $guards
|
||||
* @param string $apiToken
|
||||
* @return void
|
||||
* @throws \Illuminate\Auth\AuthenticationException
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticateApi(array $guards, string $apiToken)
|
||||
public function authenticateApi(string $apiToken): bool
|
||||
{
|
||||
if ($this->api_token !== $apiToken) {
|
||||
throw new AuthenticationException('Unauthenticated.', $guards);
|
||||
}
|
||||
if ((abs(strtotime($this->api_token_date) - time()) * 60) > (config('api.tokenTimeout') * 60)) {
|
||||
throw new AuthenticationException('Unauthenticated.', $guards);
|
||||
}
|
||||
return Hash::check($apiToken, $this->api_token) and
|
||||
((abs(strtotime($this->api_token_date) - time()) * 60) < (config('api.tokenTimeout') * 60));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Lib\Helper;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
|
|
@ -23,5 +28,39 @@ class AuthServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
Auth::viaRequest('api-token', function (Request $request) {
|
||||
// https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly/25843058
|
||||
/**
|
||||
* Un token devrais être fourni pour chaque requete apres authentification
|
||||
* api_token param est requis par Auth
|
||||
*/
|
||||
$apiToken = $request->api_token;
|
||||
|
||||
if (is_null($apiToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
** All ApiV2 routes must pass Brand model to controller action since a Brand owns
|
||||
** a service token, otherwise the api cannot be called.
|
||||
** This hack is a custom binder instead of passing
|
||||
** this to route as a parameter
|
||||
*/
|
||||
if (!($route = Helper::getRoute($request))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = Service::getFromApiToken($apiToken);
|
||||
app()->bind(Service::class, function () use ($service) {
|
||||
return $service;
|
||||
});
|
||||
$test = $service->authenticateApi($apiToken);
|
||||
return $test ? $service : null;
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c9de85693e56511fc8774b051523dcd9911a908f
|
||||
Subproject commit 5427646967e5c4e02558b564ba645263e2cb1d07
|
||||
|
|
@ -42,9 +42,7 @@ return [
|
|||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'token',
|
||||
'provider' => 'services',
|
||||
'hash' => false, // Says that tokens are already hashed in DB
|
||||
'driver' => 'api-token'
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -69,12 +67,7 @@ return [
|
|||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
'services' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\Service::class,
|
||||
],
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -83,10 +83,11 @@ class ApiBase extends BaseTestCase
|
|||
$service = $brand->services()->firstOrFail();
|
||||
|
||||
// regen bearer token to get the pwd in clear
|
||||
$service->generateBearerToken();
|
||||
$bearer_pwd = $service->bearer_pwd;
|
||||
$bearer_token = $service->generateBearerToken();
|
||||
$bearer_pwd = $service->bearer_pwd;
|
||||
$service->saveOrFail();
|
||||
$service->bearer_pwd = $bearer_pwd;
|
||||
$service->bearer_token = $bearer_token;
|
||||
$service->bearer_pwd = $bearer_pwd;
|
||||
|
||||
// get customer info
|
||||
$info = $customer->info($brand)->firstOrFail();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use App\Enums\ApiError;
|
|||
use App\Enums\ApiStatus;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Tests\CreatesApplication;
|
||||
|
||||
class ApiTest extends ApiBase
|
||||
|
|
@ -54,6 +55,6 @@ class ApiTest extends ApiBase
|
|||
$this->assertApiResponseStatusIs($json, ApiStatus::OK());
|
||||
$this->assertNotEmpty($json->json('data'));
|
||||
$this->assertNotEmpty($json->json('data.api_token'));
|
||||
$this->assertEquals(Service::findOrFail($service->id)->api_token, $json->json('data.api_token'));
|
||||
$this->assertTrue(Hash::check($json->json('data.api_token'), Service::findOrFail($service->id)->api_token));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,10 +62,11 @@ abstract class CrmClientTester extends TestCase
|
|||
$info = $customer->info($brand)->firstOrFail();
|
||||
|
||||
// regen bearer token to get the pwd in clear
|
||||
$service->generateBearerToken();
|
||||
$bearer_pwd = $service->bearer_pwd;
|
||||
$bearer_token = $service->generateBearerToken();
|
||||
$bearer_pwd = $service->bearer_pwd;
|
||||
$service->saveOrFail();
|
||||
$service->bearer_pwd = $bearer_pwd;
|
||||
$service->bearer_token = $bearer_token;
|
||||
$service->bearer_pwd = $bearer_pwd;
|
||||
|
||||
return compact('customer', 'service', 'brand', 'login', 'info');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue