. */ declare(strict_types=1); namespace FireflyIII\Services\Spectre\Request; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; use Log; use Requests; use Requests_Exception; use Requests_Response; //use FireflyIII\Services\Bunq\Object\ServerPublicKey; /** * Class BunqRequest. */ abstract class SpectreRequest { /** @var string */ protected $clientId = ''; /** * @var int */ protected $expiresAt = 0; /** @var ServerPublicKey */ protected $serverPublicKey; /** @var string */ protected $serviceSecret = ''; /** @var string */ private $privateKey = ''; /** @var string */ private $server = ''; /** @var User */ private $user; /** * SpectreRequest constructor. * * @param User $user * * @throws \Illuminate\Container\EntryNotFoundException */ public function __construct(User $user) { $this->user = $user; $this->server = config('firefly.spectre.server'); $this->expiresAt = time() + 180; $privateKey = app('preferences')->get('spectre_private_key', null); $this->privateKey = $privateKey->data; // set client ID $clientId = app('preferences')->get('spectre_client_id', null); $this->clientId = $clientId->data; // set service secret $serviceSecret = app('preferences')->get('spectre_service_secret', null); $this->serviceSecret = $serviceSecret->data; } /** * */ abstract public function call(): void; /** * @return string */ public function getClientId(): string { return $this->clientId; } /** * @param string $clientId */ public function setClientId(string $clientId): void { $this->clientId = $clientId; } /** * @return string */ public function getServer(): string { return $this->server; } /** * @return ServerPublicKey */ public function getServerPublicKey(): ServerPublicKey { return $this->serverPublicKey; } /** * @param ServerPublicKey $serverPublicKey */ public function setServerPublicKey(ServerPublicKey $serverPublicKey) { $this->serverPublicKey = $serverPublicKey; } /** * @return string */ public function getServiceSecret(): string { return $this->serviceSecret; } /** * @param string $serviceSecret */ public function setServiceSecret(string $serviceSecret): void { $this->serviceSecret = $serviceSecret; } /** * @param string $privateKey */ public function setPrivateKey(string $privateKey) { $this->privateKey = $privateKey; } /** * @param string $secret */ public function setSecret(string $secret) { $this->secret = $secret; } /** * @param string $method * @param string $uri * @param string $data * * @return string * * @throws FireflyException */ protected function generateSignature(string $method, string $uri, string $data): string { if (0 === strlen($this->privateKey)) { throw new FireflyException('No private key present.'); } if ('get' === strtolower($method) || 'delete' === strtolower($method)) { $data = ''; } // base64(sha1_signature(private_key, "Expires-at|request_method|original_url|post_body|md5_of_uploaded_file|"))) // Prepare the signature $toSign = $this->expiresAt . '|' . strtoupper($method) . '|' . $uri . '|' . $data . ''; // no file so no content there. Log::debug(sprintf('String to sign: "%s"', $toSign)); $signature = ''; // Sign the data openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA1); $signature = base64_encode($signature); return $signature; } /** * @return array */ protected function getDefaultHeaders(): array { $userAgent = sprintf('FireflyIII v%s', config('firefly.version')); return [ 'Client-Id' => $this->getClientId(), 'Service-Secret' => $this->getServiceSecret(), 'Accept' => 'application/json', 'Content-type' => 'application/json', 'Cache-Control' => 'no-cache', 'User-Agent' => $userAgent, 'Expires-at' => $this->expiresAt, ]; } /** * @param string $uri * @param array $data * * @return array * * @throws FireflyException */ protected function sendSignedSpectreGet(string $uri, array $data): array { if (0 === strlen($this->server)) { throw new FireflyException('No Spectre server defined'); } $headers = $this->getDefaultHeaders(); $body = json_encode($data); $fullUri = $this->server . $uri; $signature = $this->generateSignature('get', $fullUri, $body); $headers['Signature'] = $signature; Log::debug('Final headers for spectre signed get request:', $headers); try { $response = Requests::get($fullUri, $headers); } catch (Requests_Exception $e) { throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); } $this->detectError($response); $statusCode = intval($response->status_code); $body = $response->body; $array = json_decode($body, true); $responseHeaders = $response->headers->getAll(); $array['ResponseHeaders'] = $responseHeaders; $array['ResponseStatusCode'] = $statusCode; if (isset($array['error_class'])) { $message = $array['error_message'] ?? '(no message)'; throw new FireflyException(sprintf('Error of class %s: %s', $array['error_class'], $message)); } return $array; } /** * @param string $uri * @param array $data * * @return array * * @throws FireflyException */ protected function sendSignedSpectrePost(string $uri, array $data): array { if (0 === strlen($this->server)) { throw new FireflyException('No Spectre server defined'); } $headers = $this->getDefaultHeaders(); $body = json_encode($data); $fullUri = $this->server . $uri; $signature = $this->generateSignature('post', $fullUri, $body); $headers['Signature'] = $signature; Log::debug('Final headers for spectre signed POST request:', $headers); try { $response = Requests::get($fullUri, $headers); } catch (Requests_Exception $e) { throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); } $this->detectError($response); $body = $response->body; $array = json_decode($body, true); $responseHeaders = $response->headers->getAll(); $array['ResponseHeaders'] = $responseHeaders; $array['ResponseStatusCode'] = $response->status_code; return $array; } /** * @param Requests_Response $response * * @throws FireflyException */ private function detectError(Requests_Response $response): void { $body = $response->body; $array = json_decode($body, true); if (isset($array['error_class'])) { $message = $array['error_message'] ?? '(no message)'; throw new FireflyException(sprintf('Error of class %s: %s', $array['error_class'], $message)); } $statusCode = intval($response->status_code); if ($statusCode !== 200) { throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $response->body)); } return; } }