diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php index ee0c622..6be7a29 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -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]); } } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 52b40a3..2d45863 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -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'"); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 70d5318..bacf566 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -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) { diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index b21ce83..cd79b38 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -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(); diff --git a/app/Http/Middleware/JsonOnly.php b/app/Http/Middleware/JsonOnly.php index 5e1aa19..2930d72 100644 --- a/app/Http/Middleware/JsonOnly.php +++ b/app/Http/Middleware/JsonOnly.php @@ -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); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 61744a4..7716f4b 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -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)); } /** diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 7cebb24..21af851 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -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; + } + }); } } diff --git a/clients/V2 b/clients/V2 index c9de856..5427646 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit c9de85693e56511fc8774b051523dcd9911a908f +Subproject commit 5427646967e5c4e02558b564ba645263e2cb1d07 diff --git a/config/auth.php b/config/auth.php index b153c26..46b56f6 100644 --- a/config/auth.php +++ b/config/auth.php @@ -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, - ], + ] ], /* diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index 493d74b..545d5a6 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -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(); diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php index 7044f88..940f8b3 100644 --- a/tests/Feature/ApiV2/ApiTest.php +++ b/tests/Feature/ApiV2/ApiTest.php @@ -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)); } } diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index 0be6290..b2ec773 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -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'); }