*/ protected $fillable = [ 'name', 'bearer_token', 'bearer_pwd', 'brand_id' ]; // * EVENTS /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { static::creating(function (self $model) { self::assertFieldIsUnique('name', $model->name); $model->generateBearerToken(); $model->generateBearerPassword(); }); } // * METHODS /** * Asserts using validation that the field is unique. * * @param string $nameField * @param string $valueField * @param integer|null $id * @return void */ private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null) { $table = (new self())->getTable(); ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id); } /** * 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; } /** * 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 \RuntimeException 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(); $uri = $request->getUri(); if (app()->environment(['staging', 'production']) and Str::of($uri)->startsWith('http:')) { $uri = (string)Str::of($uri)->replaceFirst('http:', 'https:'); } $calculatedHash = ToolboxHelper::signRequest( $this->bearer_pwd, $uri, $request->getMethod(), $data ); // ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle return \hash_equals($calculatedHash, $request->signature); } // * RELATIONSHIPS /** * Get the brand that owns the service. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Brand, $this> */ public function brand() { return $this->belongsTo(Brand::class); } }