PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / api / implementors / framework.php
vikappointments / site / helpers / libraries / api / implementors Last commit date
framework.php 1 month ago index.html 1 month ago login.php 1 month ago
framework.php
457 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * VikAppointments API framework implementor.
16 * This class is used to run all the installed plugins in a given directory.
17 *
18 * All the events are runnable only if the user is correctly authenticated.
19 *
20 * @see VAPApi
21 * @see VAPApiUser
22 * @see VAPApiResponse
23 * @see VAPApiError
24 * @see VAPApiEvent
25 *
26 * @since 1.7
27 */
28 class VAPApiFramework extends VAPApi
29 {
30 /**
31 * Class constructor.
32 * @protected This class can be accessed only through the static getInstance() method.
33 *
34 * In case the framework is not accessible, it will be disabled.
35 *
36 * @param string $path The dir path containing all the plugins.
37 *
38 * @see getInstance()
39 */
40 protected function __construct($path = null)
41 {
42 parent::__construct($path);
43
44 // make sure the API framework is enabled
45 $enabled = VAPFactory::getConfig()->getBool('apifw');
46
47 if (!$enabled)
48 {
49 // disable API
50 $this->disable();
51 }
52 }
53
54 /**
55 * Authenticate the provided user and connect it on success.
56 * The credentials of the user are stored in the database.
57 *
58 * This method can raise the following internal errors:
59 * - 103 = The username and password do not match
60 * - 104 = This account is blocked
61 * - 105 = The source IP is not authorised
62 *
63 * @param VAPApiUser $user The object of the user.
64 *
65 * @return integer The ID of the user on success, otherwise false.
66 *
67 * @uses setError() Set the error raised.
68 */
69 protected function doConnection(VAPApiUser $user)
70 {
71 $dbo = JFactory::getDbo();
72
73 $q = $dbo->getQuery(true);
74
75 // get login that matches with the credentials provided
76 $q->select('*')
77 ->from($dbo->qn('#__vikappointments_api_login'))
78 ->where($dbo->qn('username') . ' = ' . $dbo->q($user->getUsername()))
79 ->where('BINARY ' . $dbo->qn('password') . ' = ' . $dbo->q($user->getPassword()));
80
81 $dbo->setQuery($q, 0, 1);
82
83 // load login
84 $login = $dbo->loadAssoc();
85
86 if (!$login)
87 {
88 // set error : credentials not correct
89 $this->setError(103, 'Authentication Error! The username and password do not match.');
90 return false;
91 }
92
93 // check if login account is still active
94 if (!$login['active'])
95 {
96 // set error : login blocked
97 $this->setError(104, 'Authentication Error! This account is blocked.');
98 return false;
99 }
100
101 // check if user IP address is in the list of the allowed IPs
102 // if there are no IPs specified, all addresses are allowed
103 if (strlen($login['ips']))
104 {
105 $ip_list = json_decode($login['ips'], true);
106
107 if (count($ip_list) && !in_array($user->getSourceIp(), $ip_list))
108 {
109 // set error : ip address not allowed
110 $this->setError(105, 'Authentication Error! The source IP is not authorised.');
111 return false;
112 }
113 }
114
115 return $login['id'];
116 }
117
118 /**
119 * Register the provided event and response.
120 * This log is registered in the database and it is visible only from the administrator.
121 *
122 * @param VAPApiEvent $event The event requested.
123 * @param VAPApiResponse $response The response caught or raised.
124 *
125 * @return boolean True if the event has been registered, otherwise false.
126 *
127 * @uses isConnected() Check if the user is connected.
128 * @uses getUser() Get the current user.
129 */
130 protected function registerEvent(?VAPApiEvent $event = null, ?VAPApiResponse $response = null)
131 {
132 $log = '';
133 $status = 2;
134 $id_user = $this->isConnected() ? $this->getUser()->id() : -1;
135 $ip = $this->isConnected() ? $this->getUser()->getSourceIp() : null;
136
137 // if the event is not empty : register it
138 if ($event !== null)
139 {
140 $log .= 'Event: ' . $event->getName() . "\n";
141 }
142
143 // if the response is not empty : register it and evaluate the status
144 if ($response !== null)
145 {
146 $log .= $response->getContent();
147
148 $status = $response->isVerified() ? 1 : 0;
149 }
150
151 if (empty($log))
152 {
153 // if the evaluated log is still empty
154 if ($id_user > 0)
155 {
156 // try to register the details of the user
157 $log = 'User [' . $this->getUser()->getUsername() . '] login @ ' . JHtml::fetch('date', 'now', 'Y-m-d H:i:s', JFactory::getApplication()->get('offset', 'UTC'));
158 }
159 else
160 {
161 // otherwise register a "unrecognised" response
162 $log = 'Unable to recognize the response';
163 }
164
165 }
166
167 // prepare log data
168 $data = array(
169 'id' => 0,
170 'id_login' => $id_user,
171 'status' => $status,
172 'content' => $log,
173 'payload' => $response->getPayload(),
174 );
175
176 // save log through model
177 return (bool) JModelVAP::getInstance('apilog')->save($data);
178 }
179
180 /**
181 * Update the user manifest after a successful authentication.
182 *
183 * @return boolean True on success, otherwise false.
184 *
185 * @uses getUser() Access the user object.
186 */
187 protected function updateUserManifest()
188 {
189 if ($this->getUser() === null)
190 {
191 return false;
192 }
193
194 // prepare login data
195 $data = array(
196 'id' => $this->getUser()->id(),
197 'last_login' => 1,
198 );
199
200 // save manifest through model
201 return JModelVAP::getInstance('apiuser')->save($data);
202 }
203
204 /**
205 * Check if the provided user has been banned.
206 * This action is executed only before the authentication.
207 * The ban is evaluated on the IP origin.
208 *
209 * A user is considered banned when its failures are equals or higher
210 * than the maximum number of failure attempts allowed.
211 *
212 * The failure attempts are always increased by the ban() function.
213 *
214 * @param VAPApiUser $user The object of the user.
215 *
216 * @return boolean True is the user is banned, otherwise false.
217 *
218 * @uses get() Get the maximum number of failure attempts from config.
219 * @see ban() Used to ban a user.
220 */
221 protected function isBanned(VAPApiUser $user)
222 {
223 // get the number of failures associated to the IP address of the user
224 $dbo = JFactory::getDbo();
225
226 $q = $dbo->getQuery(true);
227
228 $q->select($dbo->qn('fail_count'))
229 ->from($dbo->qn('#__vikappointments_api_ban'))
230 ->where($dbo->qn('ip') . ' = ' . $dbo->q($user->getSourceIp()));
231
232 $dbo->setQuery($q, 0, 1);
233
234 // if the failures count is equals or higher than the maximum allowed, it means the user
235 // needs to be banned
236 return (int) $dbo->loadResult() >= $this->get('max_failure_attempts', 10);
237 }
238
239 /**
240 * Considering this function is called after every failure, a ban is always needed.
241 * Every time this function is executed, the system will call the ban() function to apply the ban.
242 *
243 * @param VAPApiUser $user The object of the user.
244 *
245 * @return boolean Return true.
246 *
247 * @see ban() Used to ban a user.
248 */
249 protected function needBan(VAPApiUser $user)
250 {
251 // all failures need to be banned
252 // ban() function is used to increase the number of failures
253 return true;
254 }
255
256 /**
257 * Increase the failure attempts of the provided user.
258 * Once this function is terminated, the user is not effectively banned, unless its
259 * total failures are equals or higher than the maximum number allowed.
260 *
261 * @param VAPApiUser $user The object of the user.
262 *
263 * @return void
264 *
265 * @see isBanned() Check if the user is banned.
266 */
267 protected function ban(VAPApiUser $user)
268 {
269 $dbo = JFactory::getDbo();
270
271 // get the ID of the user to ban
272
273 $q = $dbo->getQuery(true);
274
275 $q->select($dbo->qn(array('id', 'fail_count')))
276 ->from($dbo->qn('#__vikappointments_api_ban'))
277 ->where($dbo->qn('ip') . ' = ' . $dbo->q($user->getSourceIp()));
278
279 $dbo->setQuery($q, 0, 1);
280 $data = $dbo->loadAssoc();
281
282 if (!$data)
283 {
284 // create new ban
285 $data = array(
286 'id' => 0,
287 'fail_count' => 0,
288 );
289 }
290
291 // increase failure count
292 $data['fail_count']++;
293
294 // save ban through model
295 JModelVAP::getInstance('apiban')->save($data);
296 }
297
298 /**
299 * Reset the count of failure attempts for the provided user.
300 *
301 * @param VAPApiUser $user The object of the user.
302 *
303 * @return boolean True if the user is correctly logged, otherwise false.
304 */
305 protected function resetBan(VAPApiUser $user)
306 {
307 if (!$user->id())
308 {
309 return false;
310 }
311
312 $dbo = JFactory::getDbo();
313
314 $q = $dbo->getQuery(true);
315
316 $q->select($dbo->qn('id'))
317 ->from($dbo->qn('#__vikappointments_api_ban'))
318 ->where($dbo->qn('ip') . ' = ' . $dbo->q($user->getSourceIp()));
319
320 $dbo->setQuery($q, 0, 1);
321 $id = (int) $dbo->loadResult();
322
323 if ($id)
324 {
325 $data = array(
326 'id' => $id,
327 'fail_count' => 0,
328 );
329
330 // reset ban through model
331 JModelVAP::getInstance('apiban')->save($data);
332 }
333
334 return true;
335 }
336
337 /**
338 * Prepares the document to output the given data.
339 *
340 * @param mixed $data The data to output.
341 * @param mixed $type The content type.
342 *
343 * @return void
344 */
345 public function output($data, $type = 'application/json')
346 {
347 if (!is_null($data))
348 {
349 $app = JFactory::getApplication();
350
351 // check whether the output requires a specific content type
352 // and make sure the headers haven't been already sent
353 if ($type && $this->sendHeaders)
354 {
355 // set content type and send the headers
356 $app->setHeader('Content-Type', $type);
357 $app->sendHeaders();
358
359 // lock headers sending
360 $this->sendHeaders = false;
361 }
362
363 // try to stringify an object in case of JSON content type
364 if (!is_string($data) && preg_match("/json/i", $type))
365 {
366 $data = json_encode($data);
367 }
368
369 echo $data;
370 }
371 }
372
373 /**
374 * Loads the configuration for the specified event and user.
375 *
376 * @param string $eventName The name of the event.
377 * @param VAPApiUser $user The object of the user.
378 *
379 * @return mixed Either an array or an object.
380 */
381 protected function loadEventConfig($eventName, ?VAPApiUser $user = null)
382 {
383 $options = array();
384
385 if (!$user)
386 {
387 // make sure we have a logged-in user
388 if (!$this->isConnected())
389 {
390 // nope, return an empty array...
391 return $options;
392 }
393
394 // use currently connected user
395 $user = $this->getUser();
396 }
397
398 // get helper model
399 $model = JModelVAP::getInstance('apiuseroptions');
400
401 // load options related to the specified ID and event
402 $data = $model->getOptions($user->id(), $eventName);
403
404 if ($data)
405 {
406 // existing record, use the stored configuration
407 $options = $data->options;
408 }
409
410 return $options;
411 }
412
413 /**
414 * Saves the configuration for the specified event and user.
415 *
416 * @param VAPApiEvent $event The event requested.
417 * @param VAPApiUser $user The object of the user.
418 *
419 * @return boolean True on success, false otherwise.
420 */
421 protected function saveEventConfig(VAPApiEvent $event, ?VAPApiUser $user = null)
422 {
423 $options = $event->getOptions();
424
425 if (!$options)
426 {
427 // empty configuration, do not need to go ahead
428 return true;
429 }
430
431 if (!$user)
432 {
433 // make sure we have a logged-in user
434 if (!$this->isConnected())
435 {
436 // nope, saving failed
437 return false;
438 }
439
440 // use currently connected user
441 $user = $this->getUser();
442 }
443
444 // get helper model
445 $model = JModelVAP::getInstance('apiuseroptions');
446
447 // set up data to bind
448 $data = array();
449 $data['id_login'] = $user->id();
450 $data['id_event'] = $event->getName();
451 $data['options'] = $event->getOptions();
452
453 // store options
454 return $model->save($data);
455 }
456 }
457