1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /** |
||
6 | * API Gateway: Cliente de API en PHP. |
||
7 | * Copyright (C) API Gateway <https://www.apigateway.cl> |
||
8 | * |
||
9 | * Este programa es software libre: usted puede redistribuirlo y/o modificarlo |
||
10 | * bajo los términos de la GNU Lesser General Public License (LGPL) publicada |
||
11 | * por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, |
||
12 | * o (a su elección) cualquier versión posterior de la misma. |
||
13 | * |
||
14 | * Este programa se distribuye con la esperanza de que sea útil, pero SIN |
||
15 | * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD |
||
16 | * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la GNU Lesser General |
||
17 | * Public License (LGPL) para obtener una información más detallada. |
||
18 | * |
||
19 | * Debería haber recibido una copia de la GNU Lesser General Public License |
||
20 | * (LGPL) junto a este programa. En caso contrario, consulte |
||
21 | * <http://www.gnu.org/licenses/lgpl.html>. |
||
22 | */ |
||
23 | |||
24 | namespace apigatewaycl\api_client; |
||
25 | |||
26 | /** |
||
27 | * Clase base para las clases que consumen la API (wrappers). |
||
28 | */ |
||
29 | class ApiBase extends ApiClient |
||
30 | { |
||
31 | /** |
||
32 | * Arreglo que contendrá el diccionario de autenticación. |
||
33 | * |
||
34 | * @var array |
||
35 | */ |
||
36 | protected $auth = []; |
||
37 | |||
38 | /** |
||
39 | * Constructor de la clase base. |
||
40 | * |
||
41 | * @param array $credenciales Argumentos adicionales para la autenticación. |
||
42 | * @param string|null $token Token de autenticación para la API. |
||
43 | * @param string|null $url URL base para la API. |
||
44 | */ |
||
45 | public function __construct( |
||
46 | array $credenciales = [], |
||
47 | string $token = null, |
||
48 | string $url = null |
||
49 | ) { |
||
50 | parent::__construct($token, $url); |
||
51 | $this->setupAuth($credenciales); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Configura la autenticación específica para la aplicación. |
||
56 | * |
||
57 | * @param array $credenciales Parámetros de autenticación. Puede ser |
||
58 | * 'pass' o 'cert'. |
||
59 | * @throws \apigatewaycl\api_client\ApiException |
||
60 | * @return void |
||
61 | */ |
||
62 | private function setupAuth(array $credenciales): void |
||
63 | { |
||
64 | $tipo = key($credenciales); // Detecta si es 'pass' o 'cert' |
||
65 | $datos = $credenciales[$tipo] ?? []; |
||
66 | |||
67 | $identificador = $datos['rut'] ?? |
||
68 | $datos['cert-data'] ?? |
||
69 | $datos['file-data'] ?? |
||
70 | null; |
||
71 | $clave = $datos['clave'] ?? |
||
72 | $datos['pkey-data'] ?? |
||
73 | $datos['file-pass'] ?? |
||
74 | null; |
||
75 | |||
76 | if ($identificador && $clave) { |
||
77 | if ($this->isAuthPass($identificador)) { |
||
78 | $this->auth = [ |
||
79 | 'pass' => [ |
||
80 | 'rut' => $identificador, |
||
81 | 'clave' => $clave, |
||
82 | ], |
||
83 | ]; |
||
84 | } elseif ($this->isAuthCertData($identificador)) { |
||
85 | $this->auth = [ |
||
86 | 'cert' => [ |
||
87 | 'cert-data' => $identificador, |
||
88 | 'pkey-data' => $clave, |
||
89 | ], |
||
90 | ]; |
||
91 | } elseif ($this->isAuthFileData($identificador)) { |
||
92 | $this->auth = [ |
||
93 | 'cert' => [ |
||
94 | 'file-data' => $identificador, |
||
95 | 'file-pass' => $clave, |
||
96 | ], |
||
97 | ]; |
||
98 | } else { |
||
99 | throw new ApiException( |
||
100 | message: 'No se han proporcionado las credenciales de autentificación.' |
||
101 | ); |
||
102 | } |
||
103 | } |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Valida la estructura de un RUT chileno utilizando una expresión regular. |
||
108 | * |
||
109 | * Este método verifica que el RUT cumpla con el formato estándar chileno, que incluye |
||
110 | * puntos como separadores de miles opcionales y un guion antes del dígito verificador. |
||
111 | * El dígito verificador puede ser un número o la letra 'K'. |
||
112 | * |
||
113 | * **Ejemplos de RUT válidos:** |
||
114 | * |
||
115 | * - 12.345.678-5 |
||
116 | * - 12345678-5 |
||
117 | * - 9.876.543-K |
||
118 | * - 9876543-K |
||
119 | * |
||
120 | * **Ejemplos de RUT inválidos:** |
||
121 | * |
||
122 | * - 12.345.678-9 (dígito verificador incorrecto) |
||
123 | * - 12345678- (falta dígito verificador) |
||
124 | * - 12345-6 (formato incorrecto) |
||
125 | * - 12.345.6785 (falta guion) |
||
126 | * - abcdefgh-i (caracteres no permitidos) |
||
127 | * |
||
128 | * @param string $rut El RUT a validar. |
||
129 | * @return bool true si el RUT tiene un formato válido,false en caso |
||
130 | * contrario. |
||
131 | */ |
||
132 | private function isAuthPass(string $rut): bool |
||
133 | { |
||
134 | if (is_null($rut)) { |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
135 | return false; |
||
136 | } |
||
137 | // Expresión regular para validar el formato del RUT chileno |
||
138 | $pattern = '/^(\d{1,3}\.?)(\d{3}\.?)(\d{3,4})-([\dkK])$/'; |
||
139 | return preg_match($pattern, $rut) === 1; |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Verifica si una cadena es una cadena codificada en Base64 válida. |
||
144 | * |
||
145 | * @param string $firmaElectronicaBase64 La cadena a verificar. |
||
146 | * @return bool true si la cadena es válida en Base64, false en caso |
||
147 | * contrario. |
||
148 | */ |
||
149 | private function isAuthFileData(string $firmaElectronicaBase64): bool |
||
150 | { |
||
151 | if (is_null($firmaElectronicaBase64)) { |
||
0 ignored issues
–
show
|
|||
152 | return false; |
||
153 | } |
||
154 | // Asegúrate de que la longitud de la cadena sea múltiplo de 4 |
||
155 | if (strlen($firmaElectronicaBase64) % 4 !== 0) { |
||
156 | return false; |
||
157 | } |
||
158 | // Validar Base64 |
||
159 | return base64_decode( |
||
160 | string: $firmaElectronicaBase64, |
||
161 | strict: true |
||
162 | ) !== false; |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Valida si una cadena tiene formato PEM válido. |
||
167 | * |
||
168 | * El formato PEM debe cumplir con los siguientes criterios: |
||
169 | * |
||
170 | * - Comienza con una línea "-----BEGIN [LABEL]-----" |
||
171 | * - Termina con una línea "-----END [LABEL]-----" |
||
172 | * - Contiene contenido Base64 válido entre las líneas BEGIN y END |
||
173 | * |
||
174 | * **Ejemplos de PEM Válidos:** |
||
175 | * |
||
176 | * ``` |
||
177 | * -----BEGIN CERTIFICATE----- |
||
178 | * MIIDdzCCAl+gAwIBAgIEbGzVnzANBgkqhkiG9w0BAQsFADBvMQswCQYDVQQGEwJV |
||
179 | * ... |
||
180 | * -----END CERTIFICATE----- |
||
181 | * ``` |
||
182 | * |
||
183 | * **Ejemplos de PEM Inválidos:** |
||
184 | * |
||
185 | * - Falta la línea de inicio o fin. |
||
186 | * - Contenido no codificado en Base64. |
||
187 | * - Etiquetas de BEGIN y END que no coinciden. |
||
188 | * |
||
189 | * @param string $pemStr La cadena a validar. |
||
190 | * @return bool true si la cadena tiene formato PEM válido, false en caso contrario. |
||
191 | */ |
||
192 | private function isAuthCertData(string $pemStr): bool |
||
193 | { |
||
194 | if (is_null($pemStr)) { |
||
0 ignored issues
–
show
|
|||
195 | return false; |
||
196 | } |
||
197 | // Expresión regular para validar el formato PEM |
||
198 | $pattern = '/-----BEGIN ([A-Z ]+)-----\s+([A-Za-z0-9+\/=\s]+)-----END \1-----$/m'; |
||
199 | if (!preg_match( |
||
200 | $pattern, |
||
201 | trim($pemStr), |
||
202 | $matches |
||
203 | ) |
||
204 | ) { |
||
205 | return false; |
||
206 | } |
||
207 | // Validar contenido Base64 |
||
208 | $base64Content = preg_replace( |
||
209 | '/\s+/', |
||
210 | '', |
||
211 | $matches[2] |
||
212 | ); |
||
213 | return base64_decode($base64Content, true) !== false; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Obtiene la autenticación de tipo 'pass'. |
||
218 | * |
||
219 | * @throws \apigatewaycl\api_client\ApiException Si falta información |
||
220 | * de autenticación. |
||
221 | * @return array Información de autenticación. |
||
222 | */ |
||
223 | protected function getAuthPass(): array |
||
224 | { |
||
225 | if (isset($this->auth['pass'])) { |
||
226 | if (empty($this->auth['pass']['rut'])) { |
||
227 | throw new ApiException(message: 'auth.pass.rut empty.'); |
||
228 | } |
||
229 | if (empty($this->auth['pass']['clave'])) { |
||
230 | throw new ApiException(message: 'auth.pass.clave empty.'); |
||
231 | } |
||
232 | } elseif (isset($this->auth['cert'])) { |
||
233 | if ( |
||
234 | isset($this->auth['cert']['cert-data']) && |
||
235 | empty($this->auth['cert']['cert-data']) |
||
236 | ) { |
||
237 | throw new ApiException(message: 'auth.cert.cert-data empty.'); |
||
238 | } |
||
239 | if ( |
||
240 | isset($this->auth['cert']['pkey-data']) && |
||
241 | empty($this->auth['cert']['pkey-data']) |
||
242 | ) { |
||
243 | throw new ApiException(message: 'auth.cert.pkey-data empty.'); |
||
244 | } |
||
245 | if ( |
||
246 | isset($this->auth['cert']['file-data']) && |
||
247 | empty($this->auth['cert']['file-data']) |
||
248 | ) { |
||
249 | throw new ApiException(message: 'auth.cert.file-data empty.'); |
||
250 | } |
||
251 | if ( |
||
252 | isset($this->auth['cert']['file-pass']) && |
||
253 | empty($this->auth['cert']['file-pass']) |
||
254 | ) { |
||
255 | throw new ApiException(message: 'auth.cert.file-pass empty.'); |
||
256 | } |
||
257 | } else { |
||
258 | throw new ApiException(message: 'auth.pass or auth.cert missing.'); |
||
259 | } |
||
260 | |||
261 | return $this->auth; |
||
262 | } |
||
263 | } |
||
264 |