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