'datetime' ]; /** * The timeout cache for this model * * @var integer */ protected $cacheFor = 0; /** * Service * * @param array $attributes */ public function __construct(array $attributes = []) { $this->cacheFor = config('api.servicesDBCache') * 60; parent::__construct($attributes); } /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { static::creating(function (self $model) { if (!$model->public_token) { $model->generatePublicToken(); } if (!$model->private_token) { $model->generatePrivateToken(); } }); static::created(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); static::updated(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); static::deleted(function () { // * Flush Eloquent cache. self::flushQueryCache(); }); } // * METHODS /** * Increment API CRM Stat * * @return void */ public function incrementStat(): void { SetApiTokenStats::dispatch($this) ->afterResponse() ->onConnection('database') ->onQueue('stats'); } /** * Generates new public token * * @return string */ public function generatePublicToken(): string { $this->public_token = hash('ripemd160', Str::uuid()); $this->token_date = now(); return $this->public_token; } /** * Generates new private token * It store the crypted private token in model * and returns the plain private token * * @return string */ public function generatePrivateToken(): string { $pwd = hash('sha256', Str::uuid()); $this->private_token = Crypt::encryptString($pwd); $this->token_date = now(); return $pwd; } /** * Get Service from bearerToken with its brand * * @param string $bearerToken * @return \App\Models\Api\ApiToken * @throws \Illuminate\Database\Eloquent\ModelNotFoundException If the token matches no known service. */ public static function getFromBearerToken(string $bearerToken): self { $apiTokens = static::all(); /** @var \App\Models\Api\ApiToken $apiToken */ foreach ($apiTokens as $apiToken) { // * Keep compatibility with V3. if ($bearerToken === $apiToken->public_token) { return $apiToken; } } throw new ModelNotFoundException('Could not find apiToken from bearer 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->private_token, $request->getUri(), $request->getMethod(), $data ); // ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle return \hash_equals($calculatedHash, $request->signature); } }