Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
42.86% covered (danger)
42.86%
3 / 7
CRAP
87.50% covered (warning)
87.50%
63 / 72
ApiInterface
0.00% covered (danger)
0.00%
0 / 1
42.86% covered (danger)
42.86%
3 / 7
22.95
87.50% covered (warning)
87.50%
63 / 72
 __construct
0.00% covered (danger)
0.00%
0 / 1
12.10
60.00% covered (warning)
60.00%
6 / 10
 call
0.00% covered (danger)
0.00%
0 / 1
3.04
83.33% covered (warning)
83.33%
5 / 6
 processRequest
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 setHeaders
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 setCredentials
0.00% covered (danger)
0.00%
0 / 1
2.50
50.00% covered (danger)
50.00%
3 / 6
 setTimestamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 calulateSignature
0.00% covered (danger)
0.00%
0 / 1
5
96.88% covered (success)
96.88%
31 / 32
1<?php
2
3namespace LDC\LDCCRM\Transport;
4
5use LDC\LDCCRM\Enums\ApiStatus;
6use LDC\LDCCRM\Exceptions\ApiErrorException;
7use LDC\LDCCRM\Exceptions\CrmException;
8use LDC\LDCCRM\Utils;
9
10/**
11 * @ignore
12 *
13 * This class contains static api credentials
14 * It handles a request to get a response
15 *
16 * @package LDCCrmClient
17 * @subpackage transport
18 */
19class ApiInterface
20{
21    /**
22     * The api endpoint url
23     * This is the CRM FQDN
24     *
25     * @var string
26     */
27    protected static $endpoint;
28
29    /**
30     * The bearer token
31     * This is related to a specific brand which allows
32     * the crm to identify the concerned brand
33     *
34     * @var string
35     */
36    protected static $bearerToken;
37
38    /**
39     * The bearer token password
40     *
41     * @var string
42     */
43    protected static $bearerPwd;
44
45    /**
46     * HTTP AUTH string
47     *
48     * @var string
49     */
50    protected static $authentification = null;
51
52    /**
53     * ApiInterface
54     *
55     * @param string|null $endpoint    The crm endpoint.
56     * @param string|null $bearerToken The crm service token.
57     * @param string|null $bearerPwd   The crm service password.
58     *
59     * @throws \LDC\LDCCRM\Exceptions\CrmException If crm.token, crm.password or
60     * crm.endpoint is missing on .env file or in laravel config.
61     */
62    public function __construct(string $endpoint = null, string $bearerToken = null, string $bearerPwd = null)
63    {
64        self::$endpoint    = self::$endpoint ?: $endpoint;
65        self::$bearerToken = self::$bearerToken ?: $bearerToken;
66        self::$bearerPwd   = self::$bearerPwd ?: $bearerPwd;
67
68        if (!self::$bearerToken or !self::$bearerPwd or !self::$endpoint) {
69            throw new CrmException(
70                'Either config or .env file need to have the token var set (crm.token, crm.password, crm.endpoint)'
71            );
72        }
73
74        // Handle HTTP Auth .
75        if (\strpos(self::$endpoint, '@')) {
76            $vars                   = \parse_url(self::$endpoint);
77            self::$authentification = \base64_encode($vars['user'] . ':' . $vars['pass']);
78        }
79    }
80
81    /**
82     * Make a crm http call to get a reponse
83     *
84     * @param \LDC\LDCCRM\Transport\Request $request
85     * @return \LDC\LDCCRM\Transport\Response
86     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
87     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
88     */
89    public function call(Request $request): Response
90    {
91        $response = $this->processRequest($request);
92
93        // * Handle response Status.
94        if ($response->status->equals(ApiStatus::KO())) {
95            if (\is_int($response->apiError)) {
96                throw new CrmException($response->message, $response->apiError);
97            } else {
98                throw new ApiErrorException($response->apiError, $response->httpCode, $response->message);
99            }
100        }
101        return $response;
102    }
103
104    /**
105     * Process request
106     *
107     * @param Request $request
108     * @return \LDC\LDCCRM\Transport\Response
109     */
110    private function processRequest(Request $request): Response
111    {
112        $this->setCredentials($request);
113        $this->setHeaders($request);
114        $this->setTimestamp($request);
115        $request->setEndpoint(self::$endpoint);
116        $request->addData([
117            'signature' => $this->calulateSignature($request)
118        ]);
119
120        /** @var \Psr\Http\Message\ResponseInterface $response */
121        $response = $request->call();
122        $status   = in_array($response->getStatusCode(), [200, 201]) ?
123            ApiStatus::OK() : ApiStatus::KO();
124
125        return new Response($status, $response);
126    }
127
128    /**
129     * Add spécific headers to he request
130     *
131     * @param \LDC\LDCCRM\Transport\Request $request
132     * @return void
133     */
134    private function setHeaders(Request &$request): void
135    {
136        $headers = [
137            'User-Agent' => sprintf('LDC CRM client version %s', Utils::getPackageInfo()['version']),
138            'Accept'     => 'application/json',
139            'Content-Type' => 'application/json'
140        ];
141        $request->addHeaders($headers);
142    }
143
144    /**
145     * Inject bearer credentials into the request
146     *
147     * @param \LDC\LDCCRM\Transport\Request $request
148     * @return void
149     */
150    protected function setCredentials(Request $request)
151    {
152        if (self::$authentification) {
153            $request->addHeaders([
154                'Authorization' => implode(',', [
155                    sprintf('Basic %s', self::$authentification)
156                ])
157            ]);
158        }
159        $request->addData(['bearer_token' => self::$bearerToken]);
160    }
161
162    /**
163     * Add a timestamp (UTC) to the request
164     *
165     * @param Request $request
166     * @return void
167     */
168    protected function setTimestamp(Request $request): void
169    {
170        $request->addData([
171            // ! UTC date time.
172            'timestamp' => \gmdate('Y-m-d H:i:s')
173        ]);
174    }
175
176    /**
177     * Calculates request signature
178     *
179     * @param Request $request
180     * @return string
181     * @throws \LDC\LDCCRM\Exceptions\CrmException In case of failure.
182     */
183    protected function calulateSignature(Request $request): string
184    {
185        $uri        = \parse_url(sprintf('%s/%s', self::$endpoint, $request->uri));
186        $method     = \strtolower($request->method->value);
187        $parameters = $request->data;
188
189        $signature = [
190            'uri' => sprintf(
191                '%s://%s%s',
192                // Assume https if non provided, security first.
193                $uri['scheme'] ?? 'https',
194                $uri['host'],
195                $uri['path'] ?? '',
196            ),
197            'method' => $method
198        ];
199
200        $flattenRequestArray = function (array $array) use (&$flattenRequestArray) {
201            $out = [];
202            foreach ($array as $key => $value) {
203                if (is_array($value)) {
204                    $subArray = $flattenRequestArray($value);
205                    foreach ($subArray as $subKey => $subValue) {
206                        $out["$key.$subKey"] = $subValue;
207                    }
208                    continue;
209                }
210                $out[$key] = $value;
211            }
212            return $out;
213        };
214
215        $parameters = $flattenRequestArray($parameters);
216
217        // ! Natural sort by keys.
218        if (!uksort($parameters, "strnatcmp")) {
219            throw new CrmException('Natural sorting the request parameters failed.');
220        }
221
222        $parameters = \array_map(function ($key, $value) {
223            return sprintf('%s=%s', \rawurlencode($key), \rawurlencode($value));
224        }, \array_keys($parameters), $parameters);
225
226        $signature[] = \implode('&', $parameters);
227
228        return base64_encode(hash_hmac(
229            'sha256',
230            \implode('\n', $signature),
231            self::$bearerPwd,
232            true
233        ));
234    }
235}