cacheFor = config('api.servicesDBCache') * 60; parent::__construct($attributes); } /** * Set the base cache tags that will be present * on all queries. * * ! This is used to flush caching * * @return array */ protected function getCacheBaseTags(): array { return [ 'incollector_services', ]; } /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { static::creating(function (self $service) { $service->generateBearerToken(); $service->generateBearerPassword(); }); static::created(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); static::updated(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); static::deleted(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); } // * RELATIONS /** * ArcadiaBrand belongsToMany relation * * @return \Illuminate\Database\Eloquent\Relations\belongsToMany */ public function arcadiaBrands() { return $this->belongsToMany(ArcadiaBrand::class); } /** * ServiceStat hasMany relation * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function servicesStats() { return $this->hasMany(ServiceStat::class); } // * METHODS /** * Increment API CRM Stat * * @return void */ public function incrementStat(): void { SetServiceStats::dispatch($this)->afterResponse() ->onConnection('database')->onQueue('stats'); } /** * Generates new bearer token * * @return string */ public function generateBearerToken(): string { $this->bearer_token = hash('ripemd160', Str::uuid()); $this->bearer_token_date = now(); return $this->bearer_token; } /** * Generates new bearer password * It store the crypted password in model * and returns the plain password * * @return string */ public function generateBearerPassword(): string { $pwd = hash('sha256', Str::uuid()); $this->bearer_pwd = Crypt::encryptString($pwd); return $pwd; } /** * Get Service from bearerToken with its brand * * @param string $bearerToken * @return \App\Models\Service * @throws \Illuminate\Database\Eloquent\ModelNotFoundException If the token matches no known service. */ public static function getFromBearerToken(string $bearerToken): self { $services = static::all(); /** @var \App\Models\Service $service */ foreach ($services as $service) { if ($service->bearer_token and $bearerToken === $service->bearer_token) { return $service->load([ 'arcadiaBrands' => function ($query) use ($service) { return $query->cacheTags(["service:{$service->id}:arcadiaBrands"]); }, ]); } } throw new ModelNotFoundException('Could not find service from Service Token'); } /** * Authenticate the request for this service * * This will check hash-mac and that the request is wellformed * * The request should have the minimum following parameters * - timestamp using format Y-m-d H:i:s, and must be UTC (Greenwich Mean Time) * - bearerToken the public token generated by the crm * - signature the hashmac signature of the request * * ! Signature must be calculated with every parameter urlencode once only * ! All parameters to calculate the signature must be sorted (natural sort asc) * * ! Signature should look like * * httpVerb\n * * uri\n * * queryString * * ! httpVerb as well as uri must be lower case * ! uri should include scheme, host and path and nothing else, if path is empty a trailing slash should be added * ! eg: https://ldc-crm.fr/customer/1 or https://ldc-crm.fr/ * ! The query string shall be separated with '&', eg param1=value1¶m2=value2 * ! Key names and values of the query string must be url encoded using RFC 3986 * ! http://www.faqs.org/rfcs/rfc3986.html * ! Signature must be hmac sha256 using the private key generatedby the crm * * * ! They should then be concat in a string separated by \n * * * * @param \Illuminate\Http\Request $request * @return boolean * @throws \App\Exceptions\Api\V2\CrmException If an error occurs. */ public function authenticateApi(Request $request): bool { // ! Hack to prevent Laravel to cast empty strings to null or the opposite. // ! If cannot decode json user legacy all() method. $data = \json_decode($request->getContent(), true); $data = (\is_array($data) and \json_last_error() === \JSON_ERROR_NONE) ? array_replace_recursive($request->all(), $data) : $request->all(); $calculatedHash = Helper::signRequest( $this->bearer_pwd, $request->getUri(), $request->getMethod(), $data ); // ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle return \hash_equals($calculatedHash, $request->signature); } }