· 6 years ago · Aug 22, 2019, 07:46 AM
1request path: http://testmcapi.connextar.com/api/user/login
2request http method: POST
3request headers: ["Content-Type": "application/json,charset=utf-8", "Content-Length": "141"]
4request parameters: ["password": "12345", "email": "chronus2011@gmail.com", "device_description": "iPhone 6", "device_identity": "4545E4B8-EDC7-4A4E-A0EA-3745F1FA401F"]
5status code: 200
6error: <html><head><title>Error: Call to undefined function Cx\Framework\Components\Security\Manager\Jwt\mcrypt_create_iv()</title><link href="//static.phalconphp.com/www/debug/3.0.x/bower_components/jquery-ui/themes/ui-lightness/jquery-ui.min.css" type="text/css" rel="stylesheet" /><link href="//static.phalconphp.com/www/debug/3.0.x/bower_components/jquery-ui/themes/ui-lightness/theme.css" type="text/css" rel="stylesheet" /><link href="//static.phalconphp.com/www/debug/3.0.x/themes/default/style.css" type="text/css" rel="stylesheet" /></head><body><div class='version'>Phalcon Framework <a href="https://docs.phalconphp.com/en/3.0.0/" target="_new">3.4.4</a></div><div align="center"><div class="error-main"><h1>Error: Call to undefined function Cx\Framework\Components\Security\Manager\Jwt\mcrypt_create_iv()</h1><span class="error-file">/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/Jwt/CxJwtAuthManager.php (306)</span></div><div class="error-info"><div id="tabs"><ul><li><a href="#error-tabs-1">Backtrace</a></li><li><a href="#error-tabs-2">Request</a></li><li><a href="#error-tabs-3">Server</a></li><li><a href="#error-tabs-4">Included Files</a></li><li><a href="#error-tabs-5">Memory</a></li></ul><div id="error-tabs-1"><table cellspacing="0" align="center" width="100%"><tr><td align="right" valign="top" class="error-number">#0</td><td><span class="error-class">Cx\Framework\Components\Security\Manager\Jwt\CxJwtAuthManager</span>-><span class="error-function">createNewToken</span>()<br/><div class="error-file">/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/Jwt/CxJwtAuthManager.php (163)</div><pre class="prettyprint highlight:1:163 linenums error-scroll"><?php
7/**
8 * Author: Andrew
9 * Date: 2017-05-12
10 */
11
12namespace Cx\Framework\Components\Security\Manager\Jwt;
13
14use Cx\Framework\Components\CxHelper;
15use Cx\Framework\Components\Routing\CxRoute;
16use Cx\Framework\Components\Security\CxAuthUserData;
17use Cx\Framework\Components\Security\iCxAuthUserData;
18use Cx\Framework\Components\Security\Manager\CxBaseAuthManager;
19use Cx\Framework\Components\Security\Provider\CxAuthProviderResult;
20use Cx\Framework\Exceptions\CxAuthException;
21use Cx\Framework\Exceptions\CxConfigurationException;
22use Cx\Framework\Exceptions\CxModelException;
23use Cx\Framework\Models\Common\Config\CxConfigAuthManager;
24use Cx\Framework\Models\Common\User\CxUser;
25use Cx\Framework\Models\Common\User\CxUserJwt;
26use Cx\Framework\Models\Common\User\CxUserRefreshToken;
27use Cx\Framework\Models\Common\User\CxUserRoutes;
28use Cx\McApi\Models\User\CxUserExtension;
29use InvalidArgumentException;
30use Lcobucci\JWT\Builder;
31use Lcobucci\JWT\Signer\Hmac\Sha256;
32use Lcobucci\JWT\Token;
33use Lcobucci\JWT\Parser;
34
35
36/**
37 * Class CxJwtAuthManager
38 * @package Cx\Framework\Components\Security\Manager\Jwt
39 *
40 * @property array $config
41 */
42class CxJwtAuthManager extends CxBaseAuthManager {
43
44 // TODO: Replace hardcoded messages with translated version
45
46 //region Properties
47
48 /**
49 * @var CxJwtAuthManagerParameters
50 */
51 private $parameters;
52
53 /**
54 * @var CxConfigAuthManager
55 */
56 private $cxConfigAuthManager;
57
58 /**
59 * @var CxJwtAuthManagerData
60 */
61 private $cxJwtAuthManagerData = null;
62
63 /**
64 * @var Token
65 */
66 private $currentToken = null;
67
68 /**
69 * Variable used to temporary store a new generated token so that
70 * it can be retrieved while building the response and sent to the client
71 *
72 * @var Token
73 */
74 private $lastGeneratedToken = null;
75
76 //endregion
77
78 // region Init and Interface methods
79
80 /**
81 * CxBasicAuth constructor.
82 * @param CxConfigAuthManager $cxConfigAuthManager
83 * @throws CxAuthException
84 */
85 public function __construct(CxConfigAuthManager $cxConfigAuthManager) {
86 // Get manager configuration parameters
87 $parameters = $cxConfigAuthManager->getParameters();
88 // Throw exception if parameters class is not correct
89 if(!($parameters instanceof CxJwtAuthManagerParameters))
90 throw new CxAuthException('Invalid parameters class supplied');
91 // Set local parameters with the supplied
92 $this->parameters = $parameters;
93 // Set local provider variable with the supplied one
94 $this->cxConfigAuthManager = $cxConfigAuthManager;
95 }
96
97 /**
98 * Handle security for the specified route
99 *
100 * @param CxRoute $cxRoute
101 * @return bool
102 * @throws CxAuthException
103 * @throws CxModelException
104 * @throws CxConfigurationException
105 */
106 public function handleRoute(CxRoute $cxRoute) {
107
108 // If the route is public we don't need security checks -> continue the dispatching loop
109 if($cxRoute->isPublic())
110 return true;
111
112 // Set current JWT token from request header
113 $this->setCurrentTokenFromRequest();
114 // If a user is not logged in or is not valid for current module
115 if(!$this->isUserLoggedInAndValidForCurrentModule()){
116 // Forward to access denied management action
117 $this->dispatcher->forward(array(
118 'controller' => 'error',
119 'namespace' => CxHelper::arrayGetValueByKey($this->router->getDefaults(), 'namespace', $this->dispatcher->getNamespaceName()),
120 'action' => 'authenticationRequired'
121 ));
122 return true;
123 }
124
125 // Log the current logged in user
126 $this->CxLogger->info('User ' . $this->CxAuth->getCurrentCxUserId() . ' is signed in. Checking flags...');
127
128 // TODO: Handle the user that navigated away from change password page (see OwlAnswers)
129
130 // TODO: Check flags and logoff/redirect to access denied if flagged (see OwlAnswers)
131
132
133
134 // Check if current user has permission for the required route and return true if permitted
135 if($this->currentUserCanAccessRoute($cxRoute->getName()))
136 return true;
137
138 // Log the access denied
139 $this->CxLogger->warning('User is not enabled to ' . $cxRoute->getName() . ' route');
140
141 // Forward to access denied management action
142 $this->dispatcher->forward(array(
143 'controller' => 'error',
144 'namespace' => CxHelper::arrayGetValueByKey($this->router->getDefaults(), 'namespace', $this->dispatcher->getNamespaceName()),
145 'action' => 'accessDenied'
146 ));
147 return true;
148
149 }
150
151 /**
152 * @return string
153 */
154 public static function getManagerDescription() {
155 return 'CX JWT authentication manager';
156 }
157
158 /**
159 * Method tha will be executed after a successful user authentication
160 *
161 * @param CxUser $cxUser
162 * @param $cxConfigAuthProviderName
163 * @param string $deviceIdentity
164 * @throws \Cx\Framework\Exceptions\CxModelException
165 */
166 public function onAuthenticatedUser(CxUser $cxUser, $cxConfigAuthProviderName, $deviceIdentity = null) {
167 $this->getCxJwtAuthManagerData()->setUserDataInfo($cxUser, $cxConfigAuthProviderName, $this->CxAuth->getCurrentModule()->getId(), $deviceIdentity);
168 $this->createNewToken();
169 }
170
171 /**
172 * Check if a user is logged in and it is valid for current module
173 *
174 * @return bool
175 */
176 public function isUserLoggedInAndValidForCurrentModule() {
177
178 // Get the currently logged in user
179 $sessionUserData = $this->getLoggedInUserData();
180
181 // If there is no logged in user return false
182 if(!$sessionUserData->getUserId())
183 return false;
184
185 // If specified module require exclusive authentication, check if user did authenticate in that module
186 if($this->CxAuth->getCurrentModule()->getRequireExclusiveAuth()){
187 return $this->getCxJwtAuthManagerData()->isUserLoggedInModule($this->CxAuth->getCurrentModule()->getId());
188 }
189
190 // User is logged in and valid for specified module
191 return true;
192 }
193
194 /**
195 * Check if the current logged in user (if any) can access the specified route
196 *
197 * @param $routeName
198 * @return bool
199 */
200 public function currentUserCanAccessRoute($routeName) {
201 return CxUserRoutes::userCanAccessRoute($this->getCxAuthUserData()->getUserId(), $routeName);
202 }
203
204 /**
205 * Method that will be called after a successful user login
206 *
207 * @return mixed
208 */
209 public function onLoginSuccess() {
210 return true;
211 }
212
213 /**
214 * @return iCxAuthUserData
215 */
216 public function getCxAuthUserData() {
217 return $this->getCxJwtAuthManagerData()->getUserData();
218 }
219
220 /**
221 * Clear all user stored data = logout
222 * @param null $options
223 * @return bool
224 */
225 public function clearUserData($options = null) {
226 $currentUserId = $this->getCxAuthUserData()->getUserId();
227 $this->getCxJwtAuthManagerData()->clear();
228 $this->currentToken = '';
229
230 // Get the device identity from options (if specified)
231 $deviceIdentity = CxHelper::arrayGetValueByKey($options, 'device_identity');
232
233 // Revoke all JWT Tokens for the current user (and device identity if specified)
234 CxUserJwt::revokeByCxUserIdAndDeviceIdentity($currentUserId, $deviceIdentity);
235 // Revoke all the Refresh tokens for the current user (and device identity if specified)
236 CxUserRefreshToken::revokeExistingByUserIdAndDeviceIdentity($currentUserId, $deviceIdentity);
237 return true;
238 }
239
240 //endregion
241
242 //region Private methods
243
244 /**
245 * Get the CxAuthUserData currently stored in the manager
246 *
247 * @return CxAuthUserData
248 */
249 private function getLoggedInUserData(){
250
251 $user = $this->getCxJwtAuthManagerData()->getUserData();
252
253 return $user;
254 }
255
256 /**
257 * Get the authentication data
258 *
259 * @return CxJwtAuthManagerData|mixed
260 */
261 private function getCxJwtAuthManagerData(){
262 // Init Auth manager data if null
263 if($this->cxJwtAuthManagerData == null || $this->cxJwtAuthManagerData->getUserData()->getUserId() == null)
264 $this->cxJwtAuthManagerData = $this->currentToken == null ? new CxJwtAuthManagerData() : CxJwtAuthManagerData::fromArray(json_decode($this->currentToken->getClaim('cxData'), true));
265 // Return the data object
266 return $this->cxJwtAuthManagerData;
267 }
268
269 //endregion
270
271 //region JWT management methods
272
273 /**
274 * Return current JWT token as string
275 *
276 * @return string
277 */
278 public function getCurrentJwtToken(){
279 return $this->currentToken ? $this->currentToken->__toString() : null;
280 }
281
282 /**
283 * Return the last generated token and than set property to null so that it will returned only once
284 *
285 * @return string|null
286 */
287 public function getAndResetLastGeneratedToken(){
288 $token = $this->lastGeneratedToken;
289 $this->lastGeneratedToken = null;
290 return $token ? $token->__toString() : null;
291 }
292
293 /**
294 * Create new JWT Token
295 * @throws \Cx\Framework\Exceptions\CxModelException
296 */
297 private function createNewToken(){
298
299 // ref: https://github.com/lcobucci/jwt/blob/3.2/README.md
300
301 // Init token signer
302 $signer = new Sha256();
303
304 // Create a new token
305 $this->currentToken = (new Builder())
306 // Configures the issuer (iss claim)
307 ->setIssuer( $this->url->getBaseUri())
308 // Configures the audience (aud claim)
309 ->setAudience($this->url->getBaseUri())
310 // Configures the id (jti claim), replicating as a header item
311 ->setId(base64_encode(mcrypt_create_iv(32)), true)
312 // Configures the time that the token was issue (iat claim)
313 ->setIssuedAt(time())
314 // Configures the time that the token can be used (nbf claim)
315 ->setNotBefore(time() + 60)
316 // Configures the expiration time of the token (nbf claim)
317 ->setExpiration(time() + $this->parameters->getTokenLifeTime())
318 // Configures empty cx data
319 ->set('cxData', json_encode($this->getCxJwtAuthManagerData()->toArray()))
320 // Sign the token
321 ->sign($signer, $this->parameters->getSecretKey())
322 // Retrieves the generated token
323 ->getToken();
324
325 // Store the generated JWT info on DB
326 $cxUserJwt = new CxUserJwt();
327 $cxUserJwt->setToken($this->currentToken->__toString());
328 $userData = $this->getCxJwtAuthManagerData()->getUserData();
329 $cxUserJwt->setDeviceIdentity($userData->getDeviceIdentity());
330 $cxUserJwt->setCxUserId($userData->getUserId());
331 $cxUserJwt->save();
332
333 // Set the new generated token as last generated token
334 $this->lastGeneratedToken = $this->currentToken;
335 }
336
337 /**
338 * @return Token|null
339 * @throws CxModelException
340 */
341 private function setCurrentTokenFromRequest(){
342 $this->currentToken = null;
343
344 $authHeader = $this->request->getHeader('Authorization');
345 if($authHeader){
346 list($jwt) = sscanf( $authHeader, 'Bearer %s');
347 try{
348 $token = (new Parser())->parse($jwt);
349 }catch(InvalidArgumentException $ex){
350 return null;
351 }
352 // Init token signer
353 $signer = new Sha256();
354
355 // If the token cannot be verified, return null
356 if(!$token->verify($signer, $this->parameters->getSecretKey()))
357 return null;
358
359 // If token is expired it is no more valid, return null
360 if($token->isExpired())
361 return null;
362
363 // If token does not exists in DB or it has been revoked, return null
364 if(!CxUserJwt::isValidToken($token->__toString()))
365 return null;
366
367 // If token is after half of his lifetime, generate a new token
368 if(intval($token->getClaim('exp') - time() < ($this->parameters->getTokenLifeTime()/2))){
369 // Get claim from existing token and generate a new one
370 $this->cxJwtAuthManagerData = CxJwtAuthManagerData::fromArray(json_decode($token->getClaim('cxData'), true));
371 $this->createNewToken();
372 }
373 else
374 $this->currentToken = $token;
375
376 }
377 return $this->currentToken;
378 }
379
380 /**
381 * Create a token that will be used as refresh token
382 *
383 * @return string
384 */
385 public static function createRefreshToken(){
386 // Init token signer
387 $signer = new Sha256();
388
389 $token = (new Builder())
390 ->setId(base64_encode(mcrypt_create_iv(32)), true)
391 ->setIssuedAt(time())
392 ->getToken();
393
394 return $token->__toString();
395 }
396
397 //endregion
398
399}</pre></td></tr><tr><td align="right" valign="top" class="error-number">#1</td><td><span class="error-class">Cx\Framework\Components\Security\Manager\Jwt\CxJwtAuthManager</span>-><span class="error-function">onAuthenticatedUser</span>(<span class="error-parameter">Object(Cx\Framework\Models\Common\User\CxUser: 33)</span>, <span class="error-parameter">CxBasicAuth</span>, <span class="error-parameter">4545E4B8-EDC7-4A4E-A0EA-3745F1FA401F</span>)<br/><div class="error-file">/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/CxAuth.php (245)</div><pre class="prettyprint highlight:1:245 linenums error-scroll"><?php
400/**
401 * Author: Andrew
402 * Date: 2017-03-28
403 */
404
405namespace Cx\Framework\Components\Security;
406
407use Cx\Framework\Components\Communication\CxCommunication;
408use Cx\Framework\Components\CxHelper;
409use Cx\Framework\Components\Logging\CxLogger;
410use Cx\Framework\Components\Routing\CxRoute;
411use Cx\Framework\Components\Routing\CxRouter;
412use Cx\Framework\Components\Security\Manager\iCxAuthManager;
413use Cx\Framework\Components\Security\Manager\Jwt\CxJwtAuthManager;
414use Cx\Framework\Components\Security\Provider\CxAuthProviderResult;
415use Cx\Framework\Components\Security\Provider\iCxAuthProvider;
416use Cx\Framework\Components\Session\CxSession;
417use Cx\Framework\Enums\CxUserConfirmationType;
418use Cx\Framework\Enums\CxUserResetPasswordType;
419use Cx\Framework\Exceptions\CxAuthException;
420use Cx\Framework\Exceptions\CxCommunicationException;
421use Cx\Framework\Exceptions\CxConfigurationException;
422use Cx\Framework\Exceptions\CxMediaException;
423use Cx\Framework\Exceptions\CxModelException;
424use Cx\Framework\Models\Common\Config\CxConfigAuthProvider;
425use Cx\Framework\Models\Common\Config\CxConfigModule;
426use Cx\Framework\Models\Common\Config\CxConfigSetting;
427use Cx\Framework\Models\Common\Settings\CxSettingFailedLoginPolicies;
428use Cx\Framework\Models\Common\Settings\CxSettingUserPolicies;
429use Cx\Framework\Models\Common\User\CxUser;
430use Cx\Framework\Models\Common\User\CxUserConfirmation;
431use Cx\Framework\Models\Common\User\CxUserLogin;
432use Cx\Framework\Models\Common\User\CxUserRefreshToken;
433use Cx\Framework\Models\Common\User\CxUserResetPassword;
434use Cx\McApi\Components\Media\CxMediaManager;
435use Phalcon\Db\Adapter;
436use Phalcon\Exception;
437use Phalcon\Http\Request;
438use Phalcon\Http\Response\Cookies;
439use Phalcon\Mvc\Router;
440use Phalcon\Mvc\User\Component;
441use Phalcon\Security;
442
443/**
444 * Class CxAuth
445 * Configured as a Phalcon shared service and engaged by the CxEventHandler during the route dispatching process.
446 * @link https://bitbucket.org/connextar/composercxframework/wiki/Security
447 *
448 * @package Cx\Framework
449 *
450 * @property Cookies $CxCookies
451 * @property CxRouter $router
452 * @property CxSession $CxSession
453 * @property CxHelper $CxHelper
454 * @property CxLogger $CxLogger
455 * @property CxAcl $CxAcl
456 * @property CxCommunication $CxCommunication
457 * @property CxMediaManager CxMediaManager
458 * @property Adapter db;
459 */
460class CxAuth extends Component {
461
462 //region Private properties
463
464 /**
465 * @var CxConfigModule null
466 */
467 private $currentModule = null;
468
469 /**
470 * @var iCxAuthUserData
471 */
472 private $currentAuthUserData = null;
473
474 /**
475 * @var iCxAuthManager
476 */
477 private $currentAuthManager = null;
478
479 /**
480 * @var CxUserActivationPolicies
481 */
482 private $userActivationPolicies = null;
483
484 //endregion
485
486 // region Init and getters/setters
487
488 /**
489 * CxAuth constructor.
490 * @throws CxAuthException
491 */
492 public function __construct() {
493
494 /**
495 * Get the current module and store it in the property
496 *
497 * @var $router Router
498 */
499 $router = $this->getDI()->get('router');
500 $module = CxConfigModule::findFirstByName($router->getModuleName());
501 if(!$module) throw new CxAuthException("Access denied. Module not found.");
502 $this->currentModule = $module;
503 }
504
505 /**
506 * Get the current executing module
507 *
508 * @return CxConfigModule
509 */
510 public function getCurrentModule(){
511 return $this->currentModule;
512 }
513
514 /**
515 * @return iCxAuthManager|null
516 * @throws CxAuthException
517 */
518 public function getAuthManager(){
519 if($this->currentAuthManager === null)
520 $this->currentAuthManager = $this->getCurrentModule()->getAuthManagerInstance();
521 return $this->currentAuthManager;
522 }
523
524 /**
525 * @return CxUserActivationPolicies|null
526 */
527 public function getUserActivationPolicies(){
528 if($this->userActivationPolicies === null)
529 $this->userActivationPolicies = CxSettingUserPolicies::loadFromDb();
530 return $this->userActivationPolicies;
531 }
532
533 //endregion
534
535 //region Routes handling
536
537 /**
538 * Handle the security related actions for route dispatch
539 *
540 * @return bool
541 * @throws CxAuthException
542 * @throws CxConfigurationException
543 */
544 public function handleRouteDispatching(){
545
546 /**
547 * Get matched route name (if any).
548 *
549 * @var $matchedRoute CxRoute
550 */
551 $matchedRoute = $this->router->getMatchedRoute();
552 // Forward to "route not found" error action
553 if(!$matchedRoute){
554 $this->dispatcher->forward(array(
555 'controller' => 'error',
556 'namespace' => CxHelper::arrayGetValueByKey($this->router->getDefaults(), 'namespace', $this->dispatcher->getNamespaceName()),
557 'action' => 'routeNotFound'
558 ));
559 return true;
560 }
561
562 // Log the matched route for debug
563 $this->CxLogger->debug("Matched route: " . $matchedRoute->getName());
564
565 // If the route support multi-language -> handle it
566 /*
567 if($matchedRoute->isMultiLanguage()){
568 //TODO: Handle language settings and parameters for the multi-language route
569 }
570 */
571
572 if($this->getAuthManager()){
573 // Delegate the route handling to the auth manager and return the result to the dispatcher
574 return $this->getAuthManager()->handleRoute($matchedRoute);
575 }
576
577 // No Auth manager -> the module has public access
578 return true;
579
580 }
581
582 /**
583 * Return true if current user (if any) can access the specified route
584 *
585 * @param $routeName
586 * @return bool
587 * @throws CxAuthException
588 */
589 public function userCanAccessRoute($routeName){
590 return $this->getAuthManager()->currentUserCanAccessRoute($routeName);
591 }
592
593 //endregion
594
595 //region Authentication and management
596
597 /**
598 * Try to authenticate the user with the specified provider
599 *
600 * @param $authProviderName
601 * @param null $data
602 * @param bool $reopenIfClosed
603 * @return int
604 * @throws CxAuthException
605 * @throws CxModelException
606 */
607 public function authenticateUser($authProviderName, $data = null){
608
609 // TODO: Replace hardcoded messages with translated version
610
611 // Check if required authentication provider exists and is supported for current module
612 $configAuthProvider = $this->getCurrentModule()->getSupportedAuthProviderByName($authProviderName);
613 if(!$configAuthProvider)
614 throw new CxAuthException('Invalid authentication provider.');
615
616 // Get the class name for the specified authentication provider and throw an error if the class does not exists
617 $authProviderClassName = $configAuthProvider->getClassName();
618 if(!class_exists($authProviderClassName))
619 throw new CxAuthException('Authentication provider class not found.', null, null, null, true);
620
621 /**
622 * Create an instance of the authentication provider class
623 * @var $authProvider iCxAuthProvider
624 */
625 $authProvider = new $authProviderClassName($configAuthProvider);
626
627 // Try to authenticate the user. Throw exception if any problem or user not found.
628 $cxAuthResult = $authProvider->authenticateAndGetResult($data ? : $this->request);
629
630 // Check if reopening closed accounts is specified
631 $reopenIfClosed = CxHelper::arrayGetValueByKey($data, 'reopen_account', false);
632
633 // If user has been successfully authenticated check flags and set the session
634 if($cxAuthResult->getResult() == CxAuthProviderResult::SUCCESS){
635 $cxUser = $cxAuthResult->getCxUserAuthProvider()->getCxUser();
636 // If CX User has not been found, throw an exception
637 if(!$cxUser) throw new CxAuthException('We could not find the user details.');
638 // Check user flags (active, banned, suspended)
639 $this->checkUserFlags($cxUser, $cxAuthResult, $reopenIfClosed);
640 // Save successful login
641 $this->saveSuccessLogin($cxUser, $configAuthProvider);
642 // Set user authentication data in the auth manager
643 $this->getAuthManager()->onAuthenticatedUser($cxUser, $authProvider::getProviderName(), $cxAuthResult->getDeviceIdentity());
644 // Return success
645 return CxAuthProviderResult::SUCCESS;
646 }
647
648 if($cxAuthResult->getResult() == CxAuthProviderResult::PARTIAL){
649
650 // If current provider does not permit automatic user mapping/creation throw an exception
651 if(!$configAuthProvider->getUserAutoCreateAllowed())
652 throw new CxAuthException('We could not find the user details.');
653
654 // Get the user authentication data
655 $cxUserAuthProvider = $cxAuthResult->getCxUserAuthProvider();
656 // Get user authentication data details
657 $identityData = $cxUserAuthProvider->getIdentityData();
658 // Check if a user mappable by email exists
659 $cxUser = CxUser::findFirstByEmail($identityData->getEmail());
660
661 // If CX User was not found we can create a new one and map it to the authentication provider
662 if(!$cxUser){
663 $cxUser = new CxUser();
664 $cxUser->setEmail($identityData->getEmail());
665 $cxUser->setFirstName($identityData->getFirstName());
666 $cxUser->setLastName($identityData->getLastName());
667 $cxUser->setActive(true);
668 $cxUser->save();
669
670 $cxUserAuthProvider->setCxUser($cxUser);
671 $cxUserAuthProvider->save();
672
673 // Check user flags (active, banned, suspended)
674 $this->checkUserFlags($cxUser, $cxAuthResult);
675 // Save successful login
676 $this->saveSuccessLogin($cxUser, $configAuthProvider);
677 // Set user authentication data in the auth manager
678 $this->getAuthManager()->onAuthenticatedUser($cxUser, $authProvider::getProviderName(), $cxAuthResult->getDeviceIdentity());
679 // Return success
680 return CxAuthProviderResult::SUCCESS;
681 }
682
683 // A user with the same email address exists -> need to map it
684
685 // TODO: Create an email confirmation request for current auth data
686
687 return CxAuthProviderResult::EMAIL_CONFIRMATION_NEEDED;
688
689 }
690
691 if($cxAuthResult->getResult() == CxAuthProviderResult::FAILED){
692 throw new CxAuthException('An error occurred while authenticating the user.');
693 }
694
695 // Shouldn't get here -> return error.
696 return CxAuthProviderResult::FAILED;
697 }
698
699 /**
700 * Set the provided user as logged in user
701 *
702 * @param CxUser $cxUser
703 * @param $authProviderName
704 * @param null $deviceIdentity
705 * @return int
706 * @throws CxAuthException
707 * @throws CxModelException
708 */
709 public function setAuthenticatedUser(CxUser $cxUser, $authProviderName, $deviceIdentity = null){
710 // Check user flags (active, banned, suspended)
711 $this->checkUserFlags($cxUser);
712 // Set user authentication data in the auth manager
713 $this->getAuthManager()->onAuthenticatedUser($cxUser, $authProviderName, $deviceIdentity);
714 // Return success
715 return CxAuthProviderResult::SUCCESS;
716 }
717
718 /**
719 * Method that will be called after a successful user authentication
720 * @throws CxAuthException
721 */
722 public function onLoginSuccess(){
723 // Execute the auth manager specific code for successful login
724 $this->getAuthManager()->onLoginSuccess();
725 // Set current logged in user data
726 $this->currentAuthUserData = $this->getAuthManager()->getCxAuthUserData();
727 }
728
729 /**
730 * Save successful CX User login to database
731 *
732 * @param CxUser $cxUser
733 * @param CxConfigAuthProvider $provider
734 * @throws CxModelException
735 */
736 public function saveSuccessLogin(CxUser $cxUser, CxConfigAuthProvider $provider) {
737 $userLogin = new CxUserLogin();
738 $userLogin->setCxUser($cxUser);
739 $userLogin->setIpAddress(self::getClientAddress($this->request));
740 $userLogin->setUserAgent(self::getUserAgent($this->request));
741 $userLogin->setCxConfigAuthProvider($provider);
742 $userLogin->setSuccess(true);
743 $userLogin->save();
744 }
745
746 /**
747 * Logout current logged in user
748 * @param null $options
749 * @throws CxAuthException
750 */
751 public function logoutUser($options = null){
752 $this->getAuthManager()->clearUserData($options);
753 }
754
755 /**
756 * Return true if the specified provider is supported for current module
757 *
758 * @param $authProviderName
759 * @return bool
760 */
761 public function isAuthProviderEnabledForCurrentModule($authProviderName){
762 return ($this->getCurrentModule()->getSupportedAuthProviderByName($authProviderName) != null);
763 }
764
765 /**
766 * Return the name of the login route for current module
767 *
768 * @return null|string
769 */
770 public function getCurrentModuleLoginRouteName(){
771 return $this->getCurrentModule()->getLoginCxConfigRouteName();
772 }
773
774 /**
775 * Return current JWT Token if supported by the auth manager, null if not supported
776 *
777 * @return string | null
778 * @throws CxAuthException
779 */
780 public function getCurrentJwtToken(){
781 // Set the JWT Token retrieval method
782 $methodName = 'getCurrentJwtToken';
783 if(method_exists($this->getAuthManager(), $methodName))
784 // The method exists, call it and return token
785 return $this->getAuthManager()->$methodName();
786 // return null for auth managers that do not support JWT Tokens
787 return null;
788 }
789
790 /**
791 * Create and return a new Refresh Token for specified device and current user
792 *
793 * @param $deviceIdentity
794 * @return CxUserRefreshToken
795 * @throws CxAuthException
796 * @throws CxModelException
797 */
798 public function getNewRefreshToken($deviceIdentity){
799
800 // Revoke all existing refresh token for current user on specified device
801 CxUserRefreshToken::revokeExistingByUserIdAndDeviceIdentity($this->getCurrentCxUserId(), $deviceIdentity);
802 // Create the new refresh token
803 $refreshToken = new CxUserRefreshToken();
804 $refreshToken->setCxUser(CxUser::findFirst($this->getCurrentCxUserId()));
805 $refreshToken->setDeviceIdentity($deviceIdentity);
806 $refreshToken->setToken(CxJwtAuthManager::createRefreshToken());
807
808 $refreshToken->save();
809 // Return token
810 return $refreshToken;
811 }
812
813 //endregion
814
815 //region Security Checking methods
816
817 /**
818 * Checks if the CX user is banned/inactive/suspended
819 *
820 * @param CxUser $user
821 * @param CxAuthProviderResult $authResult
822 * @param bool $reopenIfClosed
823 * @throws CxAuthException
824 * @throws CxModelException
825 */
826 public function checkUserFlags(CxUser $user, CxAuthProviderResult $authResult = null, $reopenIfClosed = false) {
827
828 // Check if the user account has been closed
829 if($user->isClosed() === true){
830 if($reopenIfClosed){
831 // If reopen is required, open the user
832 $user->setDateClosed(null);
833 $user->save();
834 }else{
835 throw new CxAuthException('User account closed.', CxAuthException::AUTH_EXC_ACCOUNT_CLOSED);
836 }
837 }
838
839 // Check if user is missing required confirmations
840 $this->checkRequiredConfirmations($user, $authResult);
841
842 // Throw exception if user is not active and is has no pending confirmations handled by previous check)
843 if (false === $user->isActive()) {
844 throw new CxAuthException('User account inactive.', null, null, null, true);
845 }
846
847 // Throw exception if user is banned
848 if (true === $user->isBanned()) {
849 throw new CxAuthException('User account banned. Please contact support for further details.', null, null, null, true);
850 }
851
852 // Throw exception if user is suspended
853 if (true === $user->isSuspended()) {
854 throw new CxAuthException('User account suspended. Please contact support for further details.', null, null, null, true);
855 }
856 }
857
858 /**
859 * Check if a user is logged in and valid for current module
860 *
861 * @return bool
862 * @throws CxAuthException
863 */
864 public function isUserLoggedInAndValidForCurrentModule(){
865 return $this->getAuthManager()->isUserLoggedInAndValidForCurrentModule();
866 }
867
868 /**
869 * Check if current user is allowed to perform specified action on the specified resource as specified role (or default role(s)).
870 *
871 * @param $action
872 * @param $resourceName
873 * @param null $userRoleName
874 * @param null $functionParameters
875 * @return bool
876 * @throws CxAuthException
877 * @throws CxModelException
878 */
879 public function currentUserIsAllowedTo($action, $resourceName, $userRoleName = null, $functionParameters = null){
880
881 // If no role is specified, get default user role(s)
882 if($userRoleName) $roleNames = is_array($userRoleName) ? $userRoleName : array($userRoleName);
883 else $roleNames = $this->getCurrentCxUserRoleNames();
884
885 // If any of the user roles is allowed -> stop checking and return true
886 foreach ($roleNames as $roleName){
887 if($this->CxAcl->isAllowed($roleName, $resourceName, $action, $functionParameters)){
888 return true;
889 }
890 }
891
892 // No roles enabled -> return false
893 return false;
894 }
895
896 /**
897 * Return the ACL for specified resource and current user
898 *
899 * @param string|iCxAclResource $resourceClass
900 * @return array
901 * @throws CxAuthException
902 * @throws CxModelException
903 */
904 public function currentUserAclForResource($resourceClass){
905
906 $acl = array();
907
908 // If the specified class does not exists or does not implement the iCxAclResource interface, return empty ACL
909 if(class_exists($resourceClass) && in_array(iCxAclResource::class, class_implements($resourceClass))){
910 // Get ACL for all Resource Actions and current user
911 foreach ($resourceClass::getClassResourceActions() as $action => $description){
912 $acl[$action] = CxHelper::boolToString($this->currentUserIsAllowedTo($action, $resourceClass::getClassResourceName()));
913 }
914 }
915
916 return $acl;
917 }
918
919 //endregion
920
921 //region User confirmation and activation
922
923 /**
924 * Return the name of the login route for current module
925 *
926 * @return null|string
927 */
928 public function getCurrentModuleEmailConfirmationRouteName(){
929 return $this->getCurrentModule()->getEmailConfirmationCxConfigRouteName();
930 }
931
932 /**
933 * Check if current user has missing required confirmations
934 *
935 * @param CxUser $cxUser
936 * @param CxAuthProviderResult $authResult
937 * @throws CxAuthException
938 */
939 private function checkRequiredConfirmations(CxUser $cxUser, CxAuthProviderResult $authResult = null){
940
941 // Init variable for required confirmation responses
942 $askForEmailConfirmation = false;
943 $askForPhoneConfirmation = false;
944
945 $emailConfirmationRequired = $this->getUserActivationPolicies()->isEmailConfirmationRequired() && $cxUser->needsEmailConfirmation();
946 $phoneConfirmationRequired = $this->getUserActivationPolicies()->isPhoneNumberConfirmationRequired() && $cxUser->needsPhoneNumberConfirmation();
947
948 // If both email and phone number are not confirmed and the configuration do not allow to login without both confirmations -> throw exception
949 if($emailConfirmationRequired && $phoneConfirmationRequired && !$this->getUserActivationPolicies()->isSingleConfirmationLoginAllowed()){
950 $askForEmailConfirmation = true;
951 $askForPhoneConfirmation = true;
952 } else {
953 // If email was not confirmed and the site settings require email confirmation as mandatory -> throw exception
954 if($emailConfirmationRequired && $this->getUserActivationPolicies()->isEmailConfirmationMandatory())
955 $askForEmailConfirmation = true;
956
957 // If phone number was not confirmed and the site settings require phone number confirmation as mandatory -> throw exception
958 if($phoneConfirmationRequired && $this->getUserActivationPolicies()->isPhoneNumberConfirmationMandatory())
959 $askForPhoneConfirmation = true;
960
961 // If the configuration allows to login with a single confirmation, check if the value used for login is confirmed
962 if($authResult && $this->getUserActivationPolicies()->isSingleConfirmationLoginAllowed()){
963 // If user logged in with email address and it has not been confirmed -> throw exception
964 if($authResult->isIdentifiedByEmail() && $emailConfirmationRequired)
965 $askForEmailConfirmation = true;
966 // If user logged in with phone number and it has not been confirmed -> throw exception
967 if($authResult->isIdentifiedByPhoneNumber() && $phoneConfirmationRequired)
968 $askForPhoneConfirmation = true;
969 }
970 }
971
972 // If any confirmation is required, throw related exception
973 switch(true){
974 case $askForEmailConfirmation && $askForPhoneConfirmation:
975 throw new CxAuthException('Email address and phone number confirmation is required.', CxAuthException::AUTH_EXC_EMAIL_AND_PHONE_NUMBER_CONFIRMATION_REQUIRED, null, null, true);
976 break;
977 case $askForEmailConfirmation:
978 throw new CxAuthException('Email address confirmation is required.', CxAuthException::AUTH_EXC_EMAIL_CONFIRMATION_REQUIRED, null, null, true);
979 break;
980 case $askForPhoneConfirmation:
981 throw new CxAuthException('Phone number confirmation is required.', CxAuthException::AUTH_EXC_PHONE_NUMBER_CONFIRMATION_REQUIRED, null, null, true);
982 break;
983 }
984
985 }
986
987 /**
988 * Check if the user can be activated depending on current confirmation status
989 *
990 * @param CxUser $cxUser
991 * @return bool
992 */
993 public function userCanBeActivated(CxUser $cxUser){
994
995 $emailConfirmationRequired = $this->getUserActivationPolicies()->isEmailConfirmationRequired() && $cxUser->needsEmailConfirmation();
996 $phoneConfirmationRequired = $this->getUserActivationPolicies()->isPhoneNumberConfirmationRequired() && $cxUser->needsPhoneNumberConfirmation();
997 $emailConfirmationMandatory = $this->getUserActivationPolicies()->isEmailConfirmationMandatory();
998 $phoneConfirmationMandatory = $this->getUserActivationPolicies()->isPhoneNumberConfirmationMandatory();
999
1000 // If no confirmation required, the user can be activated
1001 if(!$emailConfirmationRequired && !$phoneConfirmationRequired)
1002 return true;
1003
1004 // If email o phone is not confirmed but confirmation is mandatory for that field, user cannot be activated
1005 if(($emailConfirmationRequired && $emailConfirmationMandatory) || ($phoneConfirmationRequired && $phoneConfirmationMandatory))
1006 return false;
1007
1008 // If login with only a confirmed field is allowed, and a least a field is confirmed, the user can be activated
1009 if($this->getUserActivationPolicies()->isSingleConfirmationLoginAllowed() && (!$emailConfirmationRequired || !$phoneConfirmationRequired))
1010 return true;
1011
1012 // There are missing mandatory confirmations -> the user cannot be activated
1013 return false;
1014 }
1015
1016 /**
1017 * Send the required user confirmations (email and/or phone number)
1018 *
1019 * @param CxUser $cxUser
1020 * @param bool $signingUp
1021 * @return array
1022 * @throws CxConfigurationException
1023 * @throws CxCommunicationException
1024 */
1025 public function sendRequiredConfirmations(CxUser $cxUser, $signingUp = false){
1026
1027 $sentConfirmations = array();
1028
1029 if($this->getUserActivationPolicies()->isEmailConfirmationRequired() && $cxUser->needsEmailConfirmation()){
1030 // Expire all existing confirmation requests
1031 CxUserConfirmation::expireAllExistingByCxUserIdAndType($cxUser->getId(), new CxUserConfirmationType(CxUserConfirmationType::EMAIL_CONFIRMATION));
1032 $cxUserConfirmation = CxUserConfirmation::createNewEmailConfirmation($cxUser);
1033 if($signingUp)
1034 $confirmationCode = $this->CxCommunication->sendEmailConfirmationForSignUp($cxUserConfirmation);
1035 else
1036 $confirmationCode = $this->CxCommunication->sendEmailConfirmationForChange($cxUserConfirmation);
1037
1038 // Add the confirmation type to the sent confirmation and set the link if the provider is in "No Send" mode.
1039 $sentConfirmations[CxUserConfirmationType::EMAIL_CONFIRMATION] = $this->CxCommunication->getEmailProvider()->isInNoSendMode() ? $confirmationCode : null;
1040 }
1041
1042 if($this->getUserActivationPolicies()->isPhoneNumberConfirmationRequired() && $cxUser->needsPhoneNumberConfirmation()){
1043 // Expire all existing confirmation requests
1044 CxUserConfirmation::expireAllExistingByCxUserIdAndType($cxUser->getId(), new CxUserConfirmationType(CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION));
1045 $cxUserConfirmation = CxUserConfirmation::createNewPhoneNumberConfirmation($cxUser);
1046 if($signingUp)
1047 $confirmationCode = $this->CxCommunication->sendPhoneNumberConfirmationForSignUp($cxUserConfirmation);
1048 else
1049 $confirmationCode = $this->CxCommunication->sendPhoneNumberConfirmationForChange($cxUserConfirmation);
1050 $sentConfirmations[CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION] = $this->CxCommunication->getPhoneProvider()->isInNoSendMode() ? $confirmationCode : null;
1051 }
1052
1053 // Return the list of sent confirmations
1054 return $sentConfirmations;
1055 }
1056
1057 /**
1058 * Send a new email confirmation message to the specified email address
1059 *
1060 * @param $emailAddress
1061 * @return array|bool
1062 * @throws CxCommunicationException
1063 * @throws CxConfigurationException
1064 */
1065 public function sendEmailConfirmation($emailAddress){
1066 // Get the user for specified email address
1067 $cxUser = CxUser::findFirstByEmail($emailAddress);
1068 // If no user found for specified email, return error
1069 if(!$cxUser) return false;
1070 // If email already confirmed, do not send validation code on email
1071 if ($cxUser->needsEmailConfirmation()) {
1072 // Expire all existing confirmation requests
1073 CxUserConfirmation::expireAllExistingByCxUserIdAndType($cxUser->getId(), new CxUserConfirmationType(CxUserConfirmationType::EMAIL_CONFIRMATION));
1074 // Create new confirmation
1075 $cxUserConfirmation = CxUserConfirmation::createNewEmailConfirmation($cxUser);
1076 // Send the confirmation email
1077 $confirmationLink = $this->CxCommunication->sendEmailConfirmationForResend($cxUserConfirmation);
1078 // Add the confirmation type to the sent confirmation and set the link if the provider is in "No Send" mode.
1079 $sentConfirmation = array(
1080 CxUserConfirmationType::EMAIL_CONFIRMATION => $this->CxCommunication->getEmailProvider()->isInNoSendMode() ? $confirmationLink : null
1081 );
1082 } else {
1083 // Add the confirmation type to null if confirmation not sent
1084 $sentConfirmation = array(
1085 CxUserConfirmationType::EMAIL_CONFIRMATION => 'Email address is already confirmed.'
1086 );
1087 }
1088
1089 return $sentConfirmation;
1090 }
1091
1092 /**
1093 * Send a new phone number confirmation message to the specified phone number
1094 *
1095 * @param $phoneNumber
1096 * @return array|bool
1097 * @throws CxConfigurationException
1098 * @throws CxCommunicationException
1099 */
1100 public function sendPhoneNumberConfirmation($phoneNumber){
1101 // Get the user for specified phone number
1102 $cxUser = CxUser::findFirstByPhoneNumber($phoneNumber);
1103 // If no user found for specified phone number, return error
1104 if(!$cxUser) return false;
1105 // If phone number not confirmed, send validation code on phone
1106 if ($cxUser->needsPhoneNumberConfirmation()) {
1107 // Expire all existing confirmation requests
1108 CxUserConfirmation::expireAllExistingByCxUserIdAndType($cxUser->getId(), new CxUserConfirmationType(CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION));
1109 // Create new confirmation
1110 $cxUserConfirmation = CxUserConfirmation::createNewPhoneNumberConfirmation($cxUser);
1111 // Send the confirmation SMS
1112 $confirmationMessage = $this->CxCommunication->sendPhoneNumberConfirmationForResend($cxUserConfirmation);
1113 // Add the confirmation type to the sent confirmation and set the link if the provider is in "No Send" mode.
1114 $sentConfirmation = array(
1115 CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION => $this->CxCommunication->getPhoneProvider()->isInNoSendMode() ? $confirmationMessage : null
1116 );
1117 } else {
1118 // Add the confirmation type to null if confirmation not sent
1119 $sentConfirmation = array(
1120 CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION => 'Phone number is already confirmed.'
1121 );
1122 }
1123
1124 return $sentConfirmation;
1125 }
1126
1127
1128 /**
1129 * Verify the confirmation for the specified user confirmation type, user and code.
1130 *
1131 * @param CxUser $cxUser
1132 * @param CxUserConfirmationType $confirmationType
1133 * @param $confirmationCode
1134 * @return bool
1135 * @throws Exception
1136 */
1137 public function verifyConfirmation(CxUser $cxUser, CxUserConfirmationType $confirmationType, $confirmationCode){
1138
1139 // Get active confirmation(s) for the specified user and confirmation type
1140 $confirmations = CxUserConfirmation::getNotExpiredByCxUserIdAndType($cxUser->getId(), $confirmationType);
1141 // If no confirmation found, return error
1142 if(!$confirmations) return false;
1143 // If more than one confirmation is found, expire all and return error
1144 if(count($confirmations) !== 1){
1145 CxUserConfirmation::expireAllExistingByCxUserIdAndType($cxUser->getId(), $confirmationType);
1146 return false;
1147 }
1148
1149 /**
1150 * Get the active confirmation record
1151 * @var CxUserConfirmation $confirmation
1152 */
1153 $confirmation = $confirmations->getFirst();
1154
1155 // Return error if the field value or the code does not match with the values in the confirmation record
1156 if(!$confirmation->isValidFieldValue($cxUser->getFieldValueByConfirmationType($confirmationType)) || !$confirmation->isValidCode($confirmationCode))
1157 return false;
1158
1159 // Set the specified field as confirmed
1160 $cxUser->setConfirmedField($confirmationType);
1161
1162 // Begin transaction on DB so that if anything goes wrong we can rollback all the steps
1163 $this->db->begin();
1164
1165 try{
1166 // If the user can be activated, activate it
1167 If($this->userCanBeActivated($cxUser))
1168 $cxUser->setActive(true);
1169 // Save user
1170 $cxUser->save();
1171
1172 $confirmation->setConfirmed(true);
1173 $confirmation->setDateExpired(time());
1174 $confirmation->save();
1175 // Everything OK, commit changes
1176 $this->db->commit();
1177 }catch(Exception $ex){
1178 // Error occurred, rollback changes and re-throw exception
1179 $this->db->rollback();
1180 throw $ex;
1181 }
1182
1183 // Return successful confirmation
1184 return true;
1185 }
1186
1187 //endregion
1188
1189 //region User Password reset
1190
1191 /**
1192 * Create and send a new password reset request for the user using the specified method (email or sms)
1193 *
1194 * @param CxUser $cxUser
1195 * @param CxUserResetPasswordType $resetPasswordType
1196 * @return string
1197 * @throws CxAuthException
1198 * @throws CxCommunicationException
1199 * @throws CxConfigurationException
1200 * @throws CxModelException
1201 */
1202 public function createAndSendUserPasswordResetRequest(CxUser $cxUser, CxUserResetPasswordType $resetPasswordType){
1203
1204 $identityValue = null;
1205 switch ($resetPasswordType->getValue()){
1206 case CxUserResetPasswordType::USING_EMAIL:
1207 $identityValue = $cxUser->getEmail();
1208 break;
1209 case CxUserResetPasswordType::USING_PHONE_NUMBER:
1210 $identityValue = $cxUser->getPhoneNumber();
1211 break;
1212 default:
1213 throw new CxAuthException('Unsupported identity field for password reset.');
1214 }
1215
1216 // Expire all previously requests for same user and type
1217 CxUserResetPassword::expireAllExistingByCxUserIdAndType($cxUser->getId(), $resetPasswordType);
1218
1219 // If a reset via email is required and email was not confirmed -> return error
1220 if($resetPasswordType->getValue() == CxUserResetPasswordType::USING_EMAIL && $this->getUserActivationPolicies()->isEmailConfirmationRequired() && $cxUser->needsEmailConfirmation())
1221 throw new CxAuthException('It\'s not possible to send a password reset code to a non-confirmed email address.');
1222
1223 // If a reset via SMS is required and phone number was not confirmed -> return error
1224 if($resetPasswordType->getValue() == CxUserResetPasswordType::USING_PHONE_NUMBER && $this->getUserActivationPolicies()->isPhoneNumberConfirmationRequired() && $cxUser->needsPhoneNumberConfirmation())
1225 throw new CxAuthException('It\'s not possible to send a password reset code to a non-confirmed phone number.');
1226
1227 // Create a new reset request
1228 $cxUserResetPassword = CxUserResetPassword::createNewRequest($cxUser, $resetPasswordType, $identityValue);
1229
1230 // Send the password reset code
1231 $sentRequest = $this->CxCommunication->sendPasswordResetCode($cxUserResetPassword, $cxUser);
1232 return $sentRequest;
1233
1234 }
1235
1236 //endregion
1237
1238 //region Current user info methods
1239
1240 /**
1241 * Get current user data reloading it from auth manager if required
1242 *
1243 * @param bool $forceReload
1244 * @return iCxAuthUserData
1245 * @throws CxAuthException
1246 */
1247 public function getCurrentAuthUserData($forceReload = false){
1248
1249 if($forceReload || !$this->currentAuthUserData || !$this->currentAuthUserData->getUserId())
1250 $this->currentAuthUserData = $this->getAuthManager()->getCxAuthUserData();
1251
1252 return $this->currentAuthUserData;
1253
1254 }
1255
1256 /**
1257 * Get the ID of current logged in user (if any)
1258 *
1259 * @return int|null
1260 * @throws CxAuthException
1261 */
1262 public function getCurrentCxUserId(){
1263 return $this->getCurrentAuthUserData() ? $this->getCurrentAuthUserData()->getUserId() : null;
1264 }
1265
1266 /**
1267 * Get the Full Name of current logged in user (if any)
1268 *
1269 * @return int|null
1270 * @throws CxAuthException
1271 */
1272 public function getCurrentCxUserFullName(){
1273 return $this->getCurrentAuthUserData() ? $this->getCurrentAuthUserData()->getUserFullName() : null;
1274 }
1275
1276 /**
1277 * Get the default roles name for current logged in user (if any)
1278 *
1279 * @return array|\string[]
1280 * @throws CxAuthException
1281 */
1282 public function getCurrentCxUserRoleNames(){
1283 return $this->getCurrentAuthUserData() ? $this->getCurrentAuthUserData()->getUserDefaultRolesName() : array();
1284
1285 }
1286
1287 /**
1288 * Retrun the public path for the user profile image (if any)
1289 * @param null $default
1290 * @return string|null
1291 * @throws CxAuthException
1292 * @throws CxMediaException
1293 */
1294 public function getCurrentCxUserImagePublicPath($default = null){
1295 $userProfileImage = $this->getCurrentAuthUserData() ? $this->getCurrentAuthUserData()->getUserProfileImage() : null;
1296 return $userProfileImage ? $this->CxMediaManager->getPublicUrlForImage($userProfileImage) : $default;
1297 }
1298
1299 //endregion
1300
1301 //region Static methods
1302
1303 /**
1304 * Implements login throttling
1305 * Reduces the effectiveness of brute force attacks
1306 *
1307 * @param CxUser|null $cxUser
1308 * @param Request $request
1309 * @param CxConfigAuthProvider $provider
1310 * @throws CxModelException
1311 */
1312 public static function registerUserThrottling($cxUser, Request $request, CxConfigAuthProvider $provider) {
1313 // Save a failed login to database
1314 $userLogin = new CxUserLogin();
1315 if($cxUser) $userLogin->setCxUser($cxUser);
1316 $userLogin->setIpAddress(self::getClientAddress($request));
1317 $userLogin->setUserAgent(self::getUserAgent($request));
1318 $userLogin->setCxConfigAuthProvider($provider);
1319 $userLogin->setSuccess(false);
1320 $userLogin->save();
1321
1322 // If a user is specified, check login failed policies and actions
1323 if($cxUser) {
1324 // Get policies from configuration settings
1325 $userPolicies = CxSettingFailedLoginPolicies::loadFromDb();
1326 // Get number of attempted login during configured evaluation interval.
1327 $attempts = CxUserLogin::countLatestFailedLoginsByIpAddressAndUserId($userLogin->getIpAddress(), $cxUser->getId(), $userPolicies->getEvaluationInterval());
1328 // Evaluate the actions levels
1329 $matchedInterval = null;
1330 foreach ($userPolicies->getIntervals() as $interval){
1331 if($attempts >= $interval->getAttempts())
1332 $matchedInterval = $interval;
1333 }
1334 // If a level was matched, execute action
1335 if($matchedInterval){
1336 if($matchedInterval->getSuspend()){
1337 $cxUser->setSuspended(true);
1338 $cxUser->save();
1339 }
1340 if($matchedInterval->getDelay())
1341 sleep($matchedInterval->getDelay());
1342 }
1343 }
1344 }
1345
1346 /**
1347 * Return current request user agent or "N/A" if request or user agent is not available
1348 *
1349 * @param Request $request
1350 * @return string
1351 */
1352 public static function getUserAgent(Request $request) {
1353 return $request->getUserAgent() ? : 'N/A';
1354 }
1355
1356 /**
1357 * Return current request user agent or "N/A" if request or user agent is not available
1358 *
1359 * @param Request $request
1360 * @return string
1361 */
1362 public static function getClientAddress(Request $request) {
1363 return $request->getClientAddress() ? : 'N/A';
1364 }
1365
1366 /**
1367 * Return true if the provided password string match with the hashed one.
1368 *
1369 * @param $password
1370 * @param $hashedPassword
1371 * @return bool
1372 * @throws CxConfigurationException
1373 */
1374 public static function isPasswordValidForHash($password, $hashedPassword){
1375 /** @var Security $securityService */
1376 $securityService = CxHelper::getServiceFromDefaultDi('security');
1377 return $securityService->checkHash($password, $hashedPassword);
1378 }
1379
1380 //endregion
1381
1382}</pre></td></tr><tr><td align="right" valign="top" class="error-number">#2</td><td><span class="error-class">Cx\Framework\Components\Security\CxAuth</span>-><span class="error-function">authenticateUser</span>(<span class="error-parameter">CxBasicAuth</span>, <span class="error-parameter">Array([email] => chronus2011@gmail.com, [phone_number] => (empty string), [phone_region_code] => (empty string), [password] => 12345, [username] => chronus2011@gmail.com, [device_identity] => 4545E4B8-EDC7-4A4E-A0EA-3745F1FA401F, [device_description] => iPhone 6, [reopen_account] => )</span>)<br/><div class="error-file">/var/www/clients/client2/web16/private/apps/McApi/Controllers/ApiUserController.php (70)</div><pre class="prettyprint highlight:1:70 linenums error-scroll"><?php
1383/**
1384 * Author: Andrew
1385 * Date: 2017-08-31
1386 */
1387
1388namespace Cx\McApi\Controllers;
1389
1390use Cx\Framework\Components\Security\Provider\Basic\CxBasicAuth;
1391use Cx\Framework\Components\Security\Provider\Basic\CxBasicAuthData;
1392use Cx\Framework\Components\Security\Provider\CxAuthProviderResult;
1393use Cx\Framework\Enums\CxApiResponseStatus;
1394use Cx\Framework\Enums\CxUserConfirmationType;
1395use Cx\Framework\Exceptions\CxAuthException;
1396use Cx\Framework\Exceptions\CxCommunicationException;
1397use Cx\Framework\Exceptions\CxConfigurationException;
1398use Cx\Framework\Exceptions\CxMediaException;
1399use Cx\Framework\Exceptions\CxModelException;
1400use Cx\Framework\Models\Common\User\CxUserAuthProvider;
1401use Cx\Framework\Models\Common\User\CxUserGroup;
1402use Cx\Framework\Models\Common\User\CxUserPasswordChange;
1403use Cx\Framework\Models\Common\User\CxUserRefreshToken;
1404use Cx\Framework\Models\Common\User\CxUserResetPassword;
1405use Cx\Framework\PhpExtensions\CxDateTime;
1406use Cx\Framework\Responses\CxApiResponse;
1407use Cx\Framework\Responses\CxBaseDataResponse;
1408use Cx\McApi\ApiEntities\User\McUserData;
1409use Cx\McApi\ApiEntities\User\McUserLoginData;
1410use Cx\McApi\ApiEntities\User\McUserEmailValidationData;
1411use Cx\McApi\ApiEntities\User\McUserLogoutData;
1412use Cx\McApi\ApiEntities\User\McUserPhoneNumberValidationData;
1413use Cx\McApi\ApiEntities\User\McUserRefreshTokenData;
1414use Cx\McApi\ApiEntities\User\McUserResendCodeData;
1415use Cx\McApi\ApiEntities\User\McUserResetPasswordData;
1416use Cx\McApi\Enums\CxMcApiSubscriptionStatus;
1417use Cx\McApi\Exceptions\CxMcApiException;
1418use Cx\McApi\Models\Account\CxMcAccount;
1419use Cx\McApi\Models\Settings\CxSettingMcSubscriptions;
1420use Cx\McApi\Models\Settings\CxSettingMcDefaultUserGroups;
1421use Cx\McApi\Models\User\CxUserExtension;
1422use Phalcon\Exception;
1423
1424class ApiUserController extends ApiControllerBase {
1425
1426 //region Login/Logout/Reset Password
1427
1428 /**
1429 * Execute user login
1430 * @return CxBaseDataResponse
1431 * @throws CxAuthException
1432 * @throws CxConfigurationException
1433 * @throws CxMcApiException
1434 * @throws CxMediaException
1435 * @throws CxModelException
1436 */
1437 public function loginAction(){
1438
1439 $cxUserLoginData = McUserLoginData::createFromJson($this->request->getRawBody());
1440
1441 $loggedInCxUserExtension = null;
1442
1443 // If login data is not valid, return error
1444 if(!$cxUserLoginData->isValid())
1445 return CxApiResponse::withMessage(new CxApiResponseStatus(CxApiResponseStatus::INVALID_REQUEST), $cxUserLoginData->getValidationMessages());
1446
1447 $result = null;
1448 $reopenAccount = false;
1449
1450 try {
1451 if($this->CxAuth->authenticateUser(CxBasicAuth::getProviderName(), $cxUserLoginData->toArray()) == CxAuthProviderResult::SUCCESS){
1452 // Execute CX Auth operations for successful login
1453 $this->CxAuth->onLoginSuccess();
1454 /**
1455 * Get the logged in CX MC User
1456 * @var CxUserExtension $loggedInCxUserExtension
1457 */
1458 $loggedInCxUserExtension = CxUserExtension::findFirst($this->CxAuth->getCurrentCxUserId());
1459
1460 // Get the ID of Parent and Child default groups
1461 $defaultGroups = CxSettingMcDefaultUserGroups::loadFromDb();
1462
1463 if(!$loggedInCxUserExtension->isMemberOfGroups(array($defaultGroups->getAccountUserGroupId(), $defaultGroups->getChildUserGroupId()))){
1464 // The logged in user is not member of Account(Parent) or Child groups, deny access.
1465 $this->CxAuth->logoutUser();
1466 throw new CxAuthException('The credentials you provided are not enabled to access this app');
1467 }
1468
1469 // If the user must be reopened, do it
1470 if($reopenAccount){
1471 $loggedInCxUserExtension->setDateClosed(null);
1472 $loggedInCxUserExtension->save();
1473 }
1474 // Make sure current device is registered in the list of account devices
1475 $account = $loggedInCxUserExtension->getCxMcAccount();
1476
1477 // If the user does not have an account, throw error.
1478 if(!$account)
1479 throw new CxAuthException('The credentials you provided are not enabled to access this app');
1480
1481 $deviceIdentity = $this->CxAuth->getCurrentAuthUserData()->getDeviceIdentity();
1482 // If the device is not registered, register with the device identity as default description (if description was not specified)
1483 $currentDeviceIdentity = $account->getCxMcAccountDeviceByDeviceIdentity($deviceIdentity);
1484 if ($currentDeviceIdentity === null){
1485 $deviceDescription = $cxUserLoginData->device_description ? : $deviceIdentity;
1486 $account->setDeviceDescriptionByDeviceIdentity($deviceIdentity, $deviceDescription);
1487 }
1488
1489 }
1490 }catch(CxAuthException $ex){
1491 // Get required confirmations:
1492 $emailConfirmationRequired = $ex->getCode() == CxAuthException::AUTH_EXC_EMAIL_CONFIRMATION_REQUIRED || $ex->getCode() == CxAuthException::AUTH_EXC_EMAIL_AND_PHONE_NUMBER_CONFIRMATION_REQUIRED;
1493 $phoneConfirmationRequired = $ex->getCode() == CxAuthException::AUTH_EXC_PHONE_NUMBER_CONFIRMATION_REQUIRED || $ex->getCode() == CxAuthException::AUTH_EXC_EMAIL_AND_PHONE_NUMBER_CONFIRMATION_REQUIRED;
1494 $invalidCredentials = $ex->getCode() == CxAuthException::AUTH_EXC_INVALID_CREDENTIALS;
1495 $accountClosed = $ex->getCode() == CxAuthException::AUTH_EXC_ACCOUNT_CLOSED;
1496
1497 return CxApiResponse::InvalidRequest($ex->getStringFormattedMessages(PHP_EOL), array(
1498 'needEmailValidation' => $emailConfirmationRequired ? 1 : 0,
1499 'needPhoneValidation' => $phoneConfirmationRequired ? 1 : 0,
1500 'invalidCredentials' => $invalidCredentials ? 1 : 0,
1501 'accountClosed' => $accountClosed ? 1 : 0
1502 ));
1503 }
1504
1505 $data = array(
1506 'mcUserData' => McUserData::createFromCxUserExtension($loggedInCxUserExtension)->toArray()
1507 );
1508
1509 // Return success response with populated TsUserData
1510 return CxApiResponse::withMessageAndData(
1511 new CxApiResponseStatus(CxApiResponseStatus::VALID_REQUEST),
1512 'User successfully logged in.',
1513 $data
1514 );
1515 }
1516
1517 /**
1518 * Execute user logout
1519 * @return CxBaseDataResponse
1520 * @throws CxAuthException
1521 */
1522 public function logoutAction(){
1523
1524 // Get request data and return error if not valid
1525 $mcUserLogoutData = McUserLogoutData::createFromJson($this->request->getRawBody());
1526 if(!$mcUserLogoutData->isValid(null))
1527 return CxApiResponse::InvalidRequest($mcUserLogoutData->getValidationMessages());
1528
1529 // Logout current user
1530 $this->CxAuth->logoutUser(array('device_identity' => $mcUserLogoutData->device_identity));
1531
1532 // Return success response
1533 return CxApiResponse::ValidRequest("User successfully logged out.", array(
1534 'token' => $this->CxAuth->getCurrentJwtToken()
1535 ));
1536 }
1537
1538 /**
1539 * Create and send user password reset request
1540 *
1541 * @return CxBaseDataResponse
1542 * @throws CxAuthException
1543 * @throws CxModelException
1544 * @throws CxCommunicationException
1545 * @throws CxConfigurationException
1546 */
1547 public function resetPasswordAction(){
1548 // Get request data and return error if not valid
1549 $mcUserResetPasswordData = McUserResetPasswordData::createFromJson($this->request->getRawBody());
1550 if(!$mcUserResetPasswordData->isValid(McUserResetPasswordData::ACTION_REQUEST))
1551 return CxApiResponse::InvalidRequest($mcUserResetPasswordData->getValidationMessages());
1552
1553 // Retrieve the CxUser by specified email or phone number and return error if not found
1554 $cxUserExtension = CxUserExtension::findFirstByConfirmedEmailOrPhoneNumber($mcUserResetPasswordData->email, $mcUserResetPasswordData->phone_number);
1555 if(!$cxUserExtension)
1556 return CxApiResponse::InvalidRequest('We could not find the account details. Please ensure you have provided a valid and confirmed email address/phone number.');
1557
1558 // If the user does not have an account, return error.
1559 if(!$cxUserExtension->getCxMcAccount())
1560 return CxApiResponse::InvalidRequest('The information you provided is not related to a valid app account.');
1561
1562 // Get the required reset type
1563 $resetType = $mcUserResetPasswordData->getCxUserResetPasswordType();
1564 // Create and send the reset request
1565 $resetMessage = $this->CxAuth->createAndSendUserPasswordResetRequest($cxUserExtension, $resetType);
1566
1567 // Build response data (include generated message if specified in the site settings)
1568 $data = array();
1569 if($resetMessage)
1570 $data['sentMessage'] = $resetMessage;
1571
1572 // Return success response
1573 return CxApiResponse::ValidRequest('Password reset code successfully sent.', $data);
1574 }
1575
1576 /**
1577 * Execute the reset password for the specified user upon request code verification
1578 *
1579 * @return CxBaseDataResponse
1580 * @throws CxConfigurationException
1581 * @throws CxMediaException
1582 * @throws \Exception
1583 */
1584 public function resetPasswordExecuteAction(){
1585 // Get request data and return error if not valid
1586 $mcUserResetPasswordData = McUserResetPasswordData::createFromJson($this->request->getRawBody());
1587 if(!$mcUserResetPasswordData->isValid(McUserResetPasswordData::ACTION_EXECUTE))
1588 return CxApiResponse::InvalidRequest($mcUserResetPasswordData->getValidationMessages());
1589 // Get the reset request from DB
1590 $resetRequest = CxUserResetPassword::findFirstValidByResetRequestData($mcUserResetPasswordData->getIdentityData(), $mcUserResetPasswordData->getCxUserResetPasswordType(), $mcUserResetPasswordData->code);
1591 // If a password reset request matching specified user and code was not found, return error
1592 if(!$resetRequest)
1593 return CxApiResponse::InvalidRequest('We could not find a password reset request for the provided data. Did you enter a valid verification code? Please go back and re-enter the verification code, or use the "Resend Code" option to get a new code.');
1594 // Begin transaction on DB so that if anything goes wrong we can rollback all the steps
1595 $this->db->begin();
1596 try{
1597 // Get user record for Basic Authentication provider (supposed to be the only one supporting password)
1598 $userAuthProvider = CxUserAuthProvider::findOneByProviderNameAndCxUserId(CxBasicAuth::getProviderName(), $resetRequest->getCxUserId());
1599 /**
1600 * Get the provider related identity data for the user
1601 * @var CxBasicAuthData $identityData
1602 */
1603 $identityData = $userAuthProvider->getIdentityData();
1604 // Set the new password and save data
1605 $identityData->setHashedPasswordFromString($mcUserResetPasswordData->new_password);
1606 $userAuthProvider->setIdentityData($identityData);
1607 $userAuthProvider->save();
1608 // Get the CX User that is changing the password
1609 $cxUSer = CxUserExtension::getExistingById($resetRequest->getCxUserId());
1610 // Add a record to the password changes table
1611 $passwordChange = CxUserPasswordChange::createFromUserAndRequest($cxUSer, $this->request);
1612 $passwordChange->save();
1613 // Set the validation date on the password change request
1614 $resetRequest->setDateValidated(time());
1615 $resetRequest->save();
1616 // Everything OK, commit changes to db
1617 $this->db->commit();
1618 }catch(Exception $ex){
1619 // Error occurred, rollback db changes and return error
1620 $this->db->rollback();
1621 return CxApiResponse::ExceptionOnRequest($ex);
1622 }
1623
1624 // Return success response
1625 return CxApiResponse::ValidRequest('Your password was reset successfully.', array(
1626 'mcUserData' => McUserData::createFromCxUserExtension($cxUSer)->toArray()
1627 ));
1628 }
1629
1630 /**
1631 * Create a new refresh token
1632 *
1633 * @return CxBaseDataResponse
1634 * @throws CxModelException
1635 * @throws CxAuthException
1636 */
1637 public function getRefreshTokenAction(){
1638 // Get the request data and return error if not valid
1639 $userRefreshTokenData = McUserRefreshTokenData::createFromJson($this->request->getRawBody());
1640 if(!$userRefreshTokenData->isValid(McUserRefreshTokenData::ACTION_CREATE))
1641 return CxApiResponse::InvalidRequest($userRefreshTokenData->getValidationMessages());
1642
1643 // Create the refresh token
1644 $token = $this->CxAuth->getNewRefreshToken($userRefreshTokenData->device_identity);
1645
1646 // Return success response with created token
1647 return CxApiResponse::ValidRequest('Refresh token successfully created.', array(
1648 'refresh_token' => $token->getToken()
1649 ));
1650 }
1651
1652 /**
1653 * Generate a new JWT token upon valid refresh token and device identity
1654 *
1655 * @return CxBaseDataResponse
1656 * @throws CxModelException
1657 */
1658 public function getNewJwtTokenAction(){
1659
1660 // Get the request data and return error if not valid
1661 $userRefreshTokenData = McUserRefreshTokenData::createFromJson($this->request->getRawBody());
1662 if(!$userRefreshTokenData->isValid(McUserRefreshTokenData::ACTION_RETRIEVE))
1663 return CxApiResponse::InvalidRequest($userRefreshTokenData->getValidationMessages());
1664
1665 $refreshToken = CxUserRefreshToken::findFirstValidByDeviceIdentityAndToken($userRefreshTokenData->device_identity, $userRefreshTokenData->refresh_token);
1666 if(!$refreshToken)
1667 return CxApiResponse::InvalidRequest('Invalid refresh token.');
1668
1669 // Try to set the token user as logged in and return any authentication error
1670 try{
1671 $this->CxAuth->setAuthenticatedUser($refreshToken->getCxUser(), CxBasicAuth::getProviderName(), $userRefreshTokenData->device_identity);
1672 }catch(CxAuthException $ex){
1673 return CxApiResponse::ExceptionOnRequest($ex);
1674 }
1675
1676 return CxApiResponse::ValidRequest('Token successfully generated.');
1677 }
1678
1679 //endregion
1680
1681 //region User view/edit/create
1682
1683 /**
1684 * Create user
1685 *
1686 * @return CxBaseDataResponse
1687 * @throws CxAuthException
1688 * @throws CxConfigurationException
1689 * @throws CxMcApiException
1690 * @throws CxMediaException
1691 * @throws \Exception
1692 */
1693 public function registerUserAction(){
1694
1695 // Get request data and validate it
1696 $mcUserData = McUserData::createFromJson($this->request->getRawBody());
1697 if(!$mcUserData->isValid(McUserData::ACTION_CREATE))
1698 return $this->forwardToInvalidRequestError($mcUserData->getValidationMessages());
1699
1700 // Init the list of sent confirmations
1701 $sentConfirmations = array();
1702
1703 // Begin transaction on DB so that if anything goes wrong we can rollback all the steps
1704 $this->db->begin();
1705 try{
1706 // Create new user from request data
1707 $cxUserExtension = $mcUserData->getNewCxUserExtension();
1708 $defaultUserGroup = CxUserGroup::findFirst(CxSettingMcDefaultUserGroups::loadFromDb()->getAccountUserGroupId());
1709 if($defaultUserGroup)
1710 $cxUserExtension->addCxUserGroup($defaultUserGroup);
1711 $cxUserExtension->save();
1712 // Create new authentication record from request data
1713 $cxUserAuthProvider = $mcUserData->getCxUserAuthProvider($cxUserExtension);
1714 $cxUserAuthProvider->save();
1715 // Create a new Mc Account for the user just created
1716 $cxMcAccount = new CxMcAccount();
1717 $cxMcAccount->setCxUserExtension($cxUserExtension);
1718 // Get defaults form site settings
1719 $mcApiSubscriptions = CxSettingMcSubscriptions::loadFromDb();
1720 $cxMcAccount->setExtendedReportingExpires(CxDateTime::timestampPeriodFromNow($mcApiSubscriptions->getExtendedReportingTrialPeriod()));
1721 $cxMcAccount->setExtendedControlsExpires(CxDateTime::timestampPeriodFromNow($mcApiSubscriptions->getExtendedControlsTrialPeriod()));
1722 $cxMcAccount->setExtendedReporting(new CxMcApiSubscriptionStatus(CxMcApiSubscriptionStatus::TRIAL_ACTIVE));
1723 $cxMcAccount->setExtendedControls(new CxMcApiSubscriptionStatus(CxMcApiSubscriptionStatus::TRIAL_ACTIVE));
1724 $cxMcAccount->setTimezone($mcUserData->timezone);
1725 $cxMcAccount->save();
1726 // Send required confirmation messages to the user
1727 $sentConfirmations = $this->CxAuth->sendRequiredConfirmations($cxUserExtension, true);
1728 // Everything OK - Commit transaction
1729 $this->db->commit();
1730 }catch(Exception $ex){
1731 // An error occurred, rollback the transaction
1732 $this->db->rollback();
1733 // Return error
1734 return CxApiResponse::ExceptionOnRequest($ex);
1735 }
1736
1737 // Return success response with populated mcUserData
1738 return CxApiResponse::ValidRequest('User successfully created.', array(
1739 'mcUserData' => McUserData::createFromCxUserExtension($cxUserExtension)->toArray(),
1740 'sentConfirmations' => $sentConfirmations
1741 ));
1742
1743 }
1744
1745 /**
1746 * Get user details
1747 *
1748 * @param $id
1749 * @return CxBaseDataResponse
1750 * @throws CxAuthException
1751 * @throws CxConfigurationException
1752 * @throws CxMcApiException
1753 * @throws CxMediaException
1754 * @throws CxModelException
1755 */
1756 public function getUserAction($id){
1757
1758 // Get specified user and throw error if not found
1759 $cxUserExtension = CxUserExtension::getExistingById($id);
1760
1761 // Get user MC Account
1762 $cxMcAccount = $cxUserExtension->getCxMcAccount();
1763
1764 if(!$this->CxAuth->currentUserIsAllowedTo('VIEW', CxUserExtension::getClassResourceName(), $this->getCurrentUserRoleNameInMcAccountByAccount($cxMcAccount)))
1765 return $this->forwardToAccessDeniedError();
1766
1767 return CxApiResponse::ValidRequest('Success', array(
1768 'mcUserData' => McUserData::createFromCxUserExtension($cxUserExtension)->toArray()
1769 ));
1770 }
1771
1772 /**
1773 * Update user details
1774 *
1775 * @param $id
1776 * @return CxBaseDataResponse
1777 * @throws CxAuthException
1778 * @throws CxConfigurationException
1779 * @throws CxMcApiException
1780 * @throws CxMediaException
1781 * @throws CxModelException
1782 */
1783 public function updateUserAction($id){
1784
1785 // Get request data and validate it
1786 $mcUserData = McUserData::createFromJson($this->request->getRawBody());
1787 if(!$mcUserData->isValid(McUserData::ACTION_EDIT))
1788 return $this->forwardToInvalidRequestError($mcUserData->getValidationMessages());
1789
1790 // Get the specified used and return error if not found
1791 $cxUserExtension = CxUserExtension::getExistingById($id);
1792
1793 // If current user is not allowed to modify the specified user, return error
1794 if(!$this->CxAuth->currentUserIsAllowedTo('EDIT', CxUserExtension::getClassResourceName(), $this->getCurrentUserRoleNameInMcAccountByAccount($cxUserExtension->getCxMcAccount())))
1795 return $this->forwardToAccessDeniedError();
1796
1797 // Update user details with data provided in the request
1798 $cxUserExtension = $mcUserData->getUpdatedCxUserExtension($cxUserExtension);
1799
1800 // Begin transaction on DB so that if anything goes wrong we can rollback all the steps
1801 $this->db->begin();
1802 try{
1803 // Save the modified user data
1804 $cxUserExtension->save();
1805 // If image removal is required, delete removed image
1806 if($mcUserData->profile_image_remove)
1807 $cxUserExtension->removeProfileImage();
1808 // If new password is specified, change user password
1809 if($mcUserData->new_password){
1810 // Get current user record for Basic Authentication provider (supposed to be the only one supporting password)
1811 $userAuthProvider = CxUserAuthProvider::findOneByProviderNameAndCxUserId(CxBasicAuth::getProviderName(), $cxUserExtension->getId());
1812 /**
1813 * Get the provider related identity data for the user
1814 * @var CxBasicAuthData $identityData
1815 */
1816 $identityData = $userAuthProvider->getIdentityData();
1817 // Verify if provided current_password is correct
1818 if(!$this->security->checkHash($mcUserData->current_password, $identityData->getHashedPassword())){
1819 throw new CxAuthException('The current password you provided is incorrect.');
1820 }
1821 // Set new password an save the record
1822 $identityData->setHashedPasswordFromString($mcUserData->new_password);
1823 $userAuthProvider->setIdentityData($identityData);
1824 $userAuthProvider->save();
1825 // Add a record to the password changes table
1826 $passwordChange = CxUserPasswordChange::createFromUserAndRequest($cxUserExtension, $this->request);
1827 $passwordChange->save();
1828 }
1829 // Send required confirmation messages to the user
1830 $sentConfirmations = $this->CxAuth->sendRequiredConfirmations($cxUserExtension, false);
1831 // Everything OK - Commit transaction
1832 $this->db->commit();
1833 } catch(Exception $ex){
1834 if($this->db->isUnderTransaction())
1835 $this->db->rollback();
1836 return CxApiResponse::ExceptionOnRequest($ex);
1837 }
1838
1839 // Return success response with populated mcUserData
1840 return CxApiResponse::ValidRequest('User successfully modified.', array(
1841 'mcUserData' => McUserData::createFromCxUserExtension($cxUserExtension)->toArray(),
1842 'sentConfirmations' => $sentConfirmations
1843 ));
1844 }
1845
1846 /**
1847 * @param $id
1848 * @return CxBaseDataResponse
1849 * @throws CxAuthException
1850 * @throws CxMcApiException
1851 * @throws CxModelException
1852 */
1853 public function deleteUserAction($id){
1854 // Get the specified used and return error if not found
1855 $cxUserExtension = CxUserExtension::getExistingById($id);
1856
1857 // If current user is not allowed to modify the specified user, return error
1858 if(!$this->CxAuth->currentUserIsAllowedTo('CLOSE', CxUserExtension::getClassResourceName(), $this->getCurrentUserRoleNameInMcAccountByAccount($cxUserExtension->getCxMcAccount())))
1859 return $this->forwardToAccessDeniedError();
1860
1861 // Close the selected account
1862 $cxUserExtension->setDateClosed(time());
1863 $cxUserExtension->save();
1864
1865 // If the closed user is the current one, log it out
1866 if($this->CxAuth->getCurrentCxUserId() == $cxUserExtension->getId())
1867 $this->CxAuth->logoutUser();
1868
1869 return CxApiResponse::ValidRequest('User account successfully closed');
1870 }
1871
1872 /**
1873 * Validate user phone number
1874 * @return CxBaseDataResponse
1875 * @throws CxConfigurationException
1876 * @throws CxMcApiException
1877 * @throws CxMediaException
1878 * @throws Exception
1879 */
1880 public function validatePhoneNumberAction(){
1881
1882 // Get request parameters and return error if not valid
1883 $mcUserValidationData = McUserPhoneNumberValidationData::createFromJson($this->request->getRawBody());
1884 if(!$mcUserValidationData->isValid(null))
1885 return CxApiResponse::InvalidRequest($mcUserValidationData->getValidationMessages());
1886
1887 // Get the user specified by phone number and return error if not found
1888 $cxUserExtension = CxUserExtension::findFirstByPhoneNumber($mcUserValidationData->phone_number);
1889 if(!$cxUserExtension)
1890 throw new CxMcApiException('User with provided phone number was not found.');
1891
1892 // Try to confirm the phone number and return error if not confirmed
1893 if(!$this->CxAuth->verifyConfirmation($cxUserExtension, new CxUserConfirmationType(CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION),$mcUserValidationData->validation_code))
1894 return CxApiResponse::InvalidRequest("The specified code is not valid.");
1895
1896 // Number confirmed -> return success response
1897 return CxApiResponse::ValidRequest(
1898 "Phone number successfully confirmed.",
1899 McUserData::createFromCxUserExtension(CxUserExtension::getExistingById($cxUserExtension->getId()))->toArray()
1900 );
1901 }
1902
1903 /**
1904 * Validate user email address
1905 * @return CxBaseDataResponse
1906 * @throws CxConfigurationException
1907 * @throws CxMcApiException
1908 * @throws CxMediaException
1909 * @throws Exception
1910 */
1911 public function validateEmailAction(){
1912
1913 // Get request parameters and return error if not valid
1914 $mcUserValidationData = McUserEmailValidationData::createFromJson($this->request->getRawBody());
1915 if(!$mcUserValidationData->isValid(null))
1916 return CxApiResponse::InvalidRequest($mcUserValidationData->getValidationMessages());
1917
1918 // Get the user specified by email and return error if not found
1919 $cxUserExtension = CxUserExtension::findFirstByEmail($mcUserValidationData->email);
1920 if(!$cxUserExtension)
1921 throw new CxMcApiException('User with provided email was not found.');
1922
1923
1924 // Try to confirm the phone number and return error if not confirmed
1925 if(!$this->CxAuth->verifyConfirmation($cxUserExtension, new CxUserConfirmationType(CxUserConfirmationType::EMAIL_CONFIRMATION),$mcUserValidationData->validation_code))
1926 return CxApiResponse::InvalidRequest("The specified code is not valid.");
1927
1928 // Number confirmed -> return success response
1929 return CxApiResponse::ValidRequest(
1930 "Email address successfully confirmed.",
1931 McUserData::createFromCxUserExtension(CxUserExtension::getExistingById($cxUserExtension->getId()))->toArray()
1932 );
1933 }
1934
1935 /**
1936 * Re-send a validation code for email address or phone number
1937 *
1938 * @return CxBaseDataResponse
1939 * @throws CxConfigurationException
1940 * @throws CxCommunicationException
1941 */
1942 public function resendValidationCodeAction(){
1943 // Get request data and return error if not valid
1944 $resendCodeData = McUserResendCodeData::createFromJson($this->request->getRawBody());
1945 if(!$resendCodeData->isValid())
1946 return CxApiResponse::InvalidRequest($resendCodeData->getValidationMessages());
1947
1948 // Init variables
1949 $sentMessages = array();
1950 $emailConfirmation = false;
1951 $phoneConfirmation = false;
1952
1953 // If required, send email validation code
1954 if($resendCodeData->email){
1955 $emailConfirmation = $this->CxAuth->sendEmailConfirmation($resendCodeData->email);
1956 $sentMessages[CxUserConfirmationType::EMAIL_CONFIRMATION] =
1957 ($emailConfirmation === false) ? 'User with provided email was not found.' : $emailConfirmation[CxUserConfirmationType::EMAIL_CONFIRMATION];
1958 }
1959
1960 // If required, send phone number validation code
1961 if($resendCodeData->phone_number){
1962 $phoneConfirmation = $this->CxAuth->sendPhoneNumberConfirmation($resendCodeData->phone_number);
1963 $sentMessages[CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION] =
1964 ($phoneConfirmation === false) ? 'User with provided phone number was not found.' : $phoneConfirmation[CxUserConfirmationType::PHONE_NUMBER_CONFIRMATION];
1965 }
1966
1967 // If both email and phone number were not found, return error
1968 if($emailConfirmation === false && $phoneConfirmation === false)
1969 return CxApiResponse::InvalidRequest('We could not find the account details. Please ensure you provide a valid email address/phone number.');
1970
1971 // Return success response
1972 return CxApiResponse::ValidRequest('Validation code(s) successfully sent.', array(
1973 'sentMessage' => $sentMessages
1974 ));
1975 }
1976
1977 //endregion
1978
1979}</pre></td></tr><tr><td align="right" valign="top" class="error-number">#3</td><td><span class="error-class">Cx\McApi\Controllers\ApiUserController</span>-><span class="error-function">loginAction</span>()</td></tr><tr><td align="right" valign="top" class="error-number">#4</td><td><span class="error-class"><a target="_new" href="//api.phalconphp.com/class/Phalcon/Dispatcher.html">Phalcon\Dispatcher</a></span>-><span class="error-function">callActionMethod</span>(<span class="error-parameter">Object(Cx\McApi\Controllers\ApiUserController)</span>, <span class="error-parameter">loginAction</span>, <span class="error-parameter">Array()</span>)</td></tr><tr><td align="right" valign="top" class="error-number">#5</td><td><span class="error-class"><a target="_new" href="//api.phalconphp.com/class/Phalcon/Dispatcher.html">Phalcon\Dispatcher</a></span>-><span class="error-function">dispatch</span>()</td></tr><tr><td align="right" valign="top" class="error-number">#6</td><td><span class="error-class"><a target="_new" href="//api.phalconphp.com/class/Phalcon/Mvc/Application.html">Phalcon\Mvc\Application</a></span>-><span class="error-function">handle</span>()<br/><div class="error-file">/var/www/clients/client2/web16/web/index.php (52)</div><pre class="prettyprint highlight:1:52 linenums error-scroll"><?php
1980
1981use Phalcon\Di\FactoryDefault;
1982use Phalcon\Mvc\Application;
1983use Cx\Framework\Configuration\CxConfiguration;
1984
1985require_once "../private/vendor/autoload.php";
1986$environments = include '../private/config/environments.php';
1987
1988// Set the application environment (DEV, TEST, PROD)
1989define('ENVIRONMENT', 'DEV');
1990
1991// Disable exception handler if required, all exception will be thrown as original
1992define('DISABLE_EXCEPTION_HANDLER', $environments[ENVIRONMENT]['disable_exception_handler']);
1993
1994// Enable profiler if required
1995if($environments[ENVIRONMENT]['enable_profiler']) {
1996 // Require the session data collector
1997 require_once "../private/apps/CxFramework/DevTools/Prophiler/DataCollector/Session.php";
1998 // Init Prophiler
1999 $profiler = new \Fabfuel\Prophiler\Profiler();
2000 $profiler->addAggregator(new \Fabfuel\Prophiler\Aggregator\Database\QueryAggregator());
2001 $toolbar = new \Fabfuel\Prophiler\Toolbar($profiler);
2002 $toolbar->addDataCollector(new \Fabfuel\Prophiler\DataCollector\Request());
2003 $toolbar->addDataCollector(new \Cx\Framework\Prophiler\DataCollector\Session());
2004}
2005
2006// Enable Phalcon Debug if required
2007if($environments[ENVIRONMENT]['enable_debug']){
2008 $debug = new \Phalcon\Debug();
2009 $debug->listen();
2010}
2011
2012// Require Cx Framework Configuration Handler and Event Handler classes because they are needed before namespaces configuration
2013require_once "../private/apps/CxFramework/Configuration/CxConfiguration.php";
2014
2015/**
2016 * The FactoryDefault Dependency Injector automatically registers
2017 * the services that provide a full stack framework.
2018 */
2019$di = new FactoryDefault();
2020// Create CX Configuration class instance
2021$cxConfig = new CxConfiguration();
2022// Create application services
2023$cxConfig->createServices($di);
2024// Create Phalcon application
2025$application = new Application($di);
2026// Register application modules
2027$cxConfig->registerModules($application);
2028
2029// Output the main content
2030echo $application->handle()->getContent();
2031
2032// If profiler is enabled, render the profile bar
2033if($environments[ENVIRONMENT]['enable_profiler']) {
2034 session_commit();
2035 // Do not render profiler if not rendering an HTML file
2036 if(count(array_filter(headers_list(), function($header){
2037 return ( stripos($header, 'Content-type: text/html;') !== false );
2038 }))){
2039 echo $toolbar->render();
2040 }
2041}
2042</pre></td></tr></table></div><div id="error-tabs-2"><table cellspacing="0" align="center" class="superglobal-detail"><tr><th>Key</th><th>Value</th></tr><tr><td class="key">_url</td><td>/api/user/login</td></tr></table></div><div id="error-tabs-3"><table cellspacing="0" align="center" class="superglobal-detail"><tr><th>Key</th><th>Value</th></tr><tr><td class="key">PATH</td><td>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</td></tr><tr><td class="key">TEMP</td><td>/var/www/clients/client2/web16/tmp</td></tr><tr><td class="key">TMPDIR</td><td>/var/www/clients/client2/web16/tmp</td></tr><tr><td class="key">TMP</td><td>/var/www/clients/client2/web16/tmp</td></tr><tr><td class="key">HOSTNAME</td><td></td></tr><tr><td class="key">USER</td><td>web16</td></tr><tr><td class="key">HOME</td><td>/var/www/clients/client2/web16</td></tr><tr><td class="key">HTTP_ACCEPT_ENCODING</td><td>gzip, deflate</td></tr><tr><td class="key">HTTP_ACCEPT_LANGUAGE</td><td>en-us</td></tr><tr><td class="key">HTTP_CONTENT_LENGTH</td><td>141</td></tr><tr><td class="key">HTTP_USER_AGENT</td><td>MediaControl/67 CFNetwork/978.0.7 Darwin/18.6.0</td></tr><tr><td class="key">HTTP_ACCEPT</td><td>*/*</td></tr><tr><td class="key">HTTP_CONNECTION</td><td>keep-alive</td></tr><tr><td class="key">HTTP_CONTENT_TYPE</td><td>application/json,charset=utf-8</td></tr><tr><td class="key">HTTP_HOST</td><td>testmcapi.connextar.com</td></tr><tr><td class="key">SCRIPT_FILENAME</td><td>/var/www/testmcapi.connextar.com/web/index.php</td></tr><tr><td class="key">REDIRECT_STATUS</td><td>200</td></tr><tr><td class="key">SERVER_NAME</td><td>testmcapi.connextar.com</td></tr><tr><td class="key">SERVER_PORT</td><td>80</td></tr><tr><td class="key">SERVER_ADDR</td><td>109.203.109.20</td></tr><tr><td class="key">REMOTE_PORT</td><td>36463</td></tr><tr><td class="key">REMOTE_ADDR</td><td>193.53.83.39</td></tr><tr><td class="key">SERVER_SOFTWARE</td><td>nginx/1.6.2</td></tr><tr><td class="key">GATEWAY_INTERFACE</td><td>CGI/1.1</td></tr><tr><td class="key">SERVER_PROTOCOL</td><td>HTTP/1.1</td></tr><tr><td class="key">DOCUMENT_ROOT</td><td>/var/www/testmcapi.connextar.com/web</td></tr><tr><td class="key">DOCUMENT_URI</td><td>/index.php</td></tr><tr><td class="key">REQUEST_URI</td><td>/api/user/login</td></tr><tr><td class="key">SCRIPT_NAME</td><td>/index.php</td></tr><tr><td class="key">CONTENT_LENGTH</td><td>141</td></tr><tr><td class="key">CONTENT_TYPE</td><td>application/json,charset=utf-8</td></tr><tr><td class="key">REQUEST_METHOD</td><td>POST</td></tr><tr><td class="key">QUERY_STRING</td><td>_url=/api/user/login&</td></tr><tr><td class="key">FCGI_ROLE</td><td>RESPONDER</td></tr><tr><td class="key">PHP_SELF</td><td>/index.php</td></tr><tr><td class="key">REQUEST_TIME_FLOAT</td><td>1566459420.6161</td></tr><tr><td class="key">REQUEST_TIME</td><td>1566459420</td></tr></table></div><div id="error-tabs-4"><table cellspacing="0" align="center" class="superglobal-detail"><tr><th>#</th><th>Path</th></tr><tr><td>0</th><td>/var/www/clients/client2/web16/web/index.php</td></tr><tr><td>1</th><td>/var/www/clients/client2/web16/private/vendor/autoload.php</td></tr><tr><td>2</th><td>/var/www/clients/client2/web16/private/vendor/composer/autoload_real.php</td></tr><tr><td>3</th><td>/var/www/clients/client2/web16/private/vendor/composer/ClassLoader.php</td></tr><tr><td>4</th><td>/var/www/clients/client2/web16/private/vendor/composer/autoload_static.php</td></tr><tr><td>5</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/promises/src/functions_include.php</td></tr><tr><td>6</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/promises/src/functions.php</td></tr><tr><td>7</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/psr7/src/functions_include.php</td></tr><tr><td>8</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/psr7/src/functions.php</td></tr><tr><td>9</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/guzzle/src/functions_include.php</td></tr><tr><td>10</th><td>/var/www/clients/client2/web16/private/vendor/guzzlehttp/guzzle/src/functions.php</td></tr><tr><td>11</th><td>/var/www/clients/client2/web16/private/vendor/swiftmailer/swiftmailer/lib/swift_required.php</td></tr><tr><td>12</th><td>/var/www/clients/client2/web16/private/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php</td></tr><tr><td>13</th><td>/var/www/clients/client2/web16/private/vendor/facebook/graph-sdk/src/Facebook/polyfills.php</td></tr><tr><td>14</th><td>/var/www/clients/client2/web16/private/config/environments.php</td></tr><tr><td>15</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Configuration/CxConfiguration.php</td></tr><tr><td>16</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Configuration/CxConfig.php</td></tr><tr><td>17</th><td>/var/www/clients/client2/web16/private/config/database.php</td></tr><tr><td>18</th><td>/var/www/clients/client2/web16/private/config/database_audit.php</td></tr><tr><td>19</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Filters/CxGreaterThanZeroFilter.php</td></tr><tr><td>20</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Routing/CxRouter.php</td></tr><tr><td>21</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Routing/CxRoute.php</td></tr><tr><td>22</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/CxHelper.php</td></tr><tr><td>23</th><td>/var/www/clients/client2/web16/private/apps/McApi/Module.php</td></tr><tr><td>24</th><td>/var/www/clients/client2/web16/private/apps/McApi/Config/loader.php</td></tr><tr><td>25</th><td>/var/www/clients/client2/web16/private/apps/McApi/Config/services.php</td></tr><tr><td>26</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/CxEventHandler.php</td></tr><tr><td>27</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/CxAuth.php</td></tr><tr><td>28</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigModule.php</td></tr><tr><td>29</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/CxResourceBaseModel.php</td></tr><tr><td>30</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/CxBaseModel.php</td></tr><tr><td>31</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Traits/CxResourceAware.php</td></tr><tr><td>32</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/iCxAclResource.php</td></tr><tr><td>33</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Model/Manager.php</td></tr><tr><td>34</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Behaviors/CxAuditModelBehavior.php</td></tr><tr><td>35</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigAuthProvider.php</td></tr><tr><td>36</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Settings/CxSettingLoggerOptions.php</td></tr><tr><td>37</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/CxSettingBase.php</td></tr><tr><td>38</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/CxSettingElementBase.php</td></tr><tr><td>39</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigSetting.php</td></tr><tr><td>40</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Logging/CxLogger.php</td></tr><tr><td>41</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Logging/Adapter/CxLoggerDatabaseAdapter.php</td></tr><tr><td>42</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Logging/Adapter/CxLoggerEmailAdapter.php</td></tr><tr><td>43</th><td>/var/www/clients/client2/web16/private/vendor/phalcon-ext/mailer/src/Manager.php</td></tr><tr><td>44</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigAuthManager.php</td></tr><tr><td>45</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/Jwt/CxJwtAuthManager.php</td></tr><tr><td>46</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/CxBaseAuthManager.php</td></tr><tr><td>47</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/iCxAuthManager.php</td></tr><tr><td>48</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/Jwt/CxJwtAuthManagerParameters.php</td></tr><tr><td>49</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/CxBaseAuthManagerParameters.php</td></tr><tr><td>50</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/iCxAuthManagerParameters.php</td></tr><tr><td>51</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/Jwt/CxJwtAuthManagerData.php</td></tr><tr><td>52</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Manager/iCxAuthManagerData.php</td></tr><tr><td>53</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/CxAuthUserData.php</td></tr><tr><td>54</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/iCxAuthUserData.php</td></tr><tr><td>55</th><td>/var/www/clients/client2/web16/private/apps/McApi/Controllers/ApiUserController.php</td></tr><tr><td>56</th><td>/var/www/clients/client2/web16/private/apps/McApi/Controllers/ApiControllerBase.php</td></tr><tr><td>57</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Controllers/CxJsonControllerBase.php</td></tr><tr><td>58</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Controllers/CxControllerBase.php</td></tr><tr><td>59</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Settings/CxSettingCorsPolicies.php</td></tr><tr><td>60</th><td>/var/www/clients/client2/web16/private/apps/McApi/ApiEntities/User/McUserLoginData.php</td></tr><tr><td>61</th><td>/var/www/clients/client2/web16/private/apps/McApi/ApiEntities/ApiEntityBase.php</td></tr><tr><td>62</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Communication/CxPhoneNumber.php</td></tr><tr><td>63</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/PhoneNumberUtil.php</td></tr><tr><td>64</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/CountryCodeToRegionCodeMap.php</td></tr><tr><td>65</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/DefaultMetadataLoader.php</td></tr><tr><td>66</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/MetadataLoaderInterface.php</td></tr><tr><td>67</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/MultiFileMetadataSourceImpl.php</td></tr><tr><td>68</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/MetadataSourceInterface.php</td></tr><tr><td>69</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/RegexBasedMatcher.php</td></tr><tr><td>70</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/MatcherAPIInterface.php</td></tr><tr><td>71</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/PhoneNumber.php</td></tr><tr><td>72</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/CountryCodeSource.php</td></tr><tr><td>73</th><td>/var/www/clients/client2/web16/private/vendor/giggsey/libphonenumber-for-php/src/NumberParseException.php</td></tr><tr><td>74</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Validators/AdvancedPresenceOf.php</td></tr><tr><td>75</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Validators/InternationalPhoneNumber.php</td></tr><tr><td>76</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/Basic/CxBasicAuth.php</td></tr><tr><td>77</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/CxBaseAuthProvider.php</td></tr><tr><td>78</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/iCxAuthProvider.php</td></tr><tr><td>79</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigModulesAuthProviders.php</td></tr><tr><td>80</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/Basic/CxBasicAuthParameters.php</td></tr><tr><td>81</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/CxBaseAuthProviderParameters.php</td></tr><tr><td>82</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/iCxAuthProviderParameters.php</td></tr><tr><td>83</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/Basic/CxBasicAuthCredentials.php</td></tr><tr><td>84</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/User/CxUserAuthProvider.php</td></tr><tr><td>85</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/User/CxUser.php</td></tr><tr><td>86</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/Basic/CxBasicAuthData.php</td></tr><tr><td>87</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/iCxAuthData.php</td></tr><tr><td>88</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Security/Provider/CxAuthProviderResult.php</td></tr><tr><td>89</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Settings/CxSettingUserPolicies.php</td></tr><tr><td>90</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/User/CxUserLogin.php</td></tr><tr><td>91</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Media/CxMediaImage.php</td></tr><tr><td>92</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Logging/CxAudit.php</td></tr><tr><td>93</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Audit/CxAuditResource.php</td></tr><tr><td>94</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Audit/CxAuditProperty.php</td></tr><tr><td>95</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Components/Logging/CxAuditColumnDefinition.php</td></tr><tr><td>96</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/User/CxUserGroup.php</td></tr><tr><td>97</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/User/CxUserGroupUsers.php</td></tr><tr><td>98</th><td>/var/www/clients/client2/web16/private/apps/CxFramework/Models/Common/Config/CxConfigRole.php</td></tr><tr><td>99</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php</td></tr><tr><td>100</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Signer/Hmac.php</td></tr><tr><td>101</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Signer/BaseSigner.php</td></tr><tr><td>102</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Signer.php</td></tr><tr><td>103</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Builder.php</td></tr><tr><td>104</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Parsing/Encoder.php</td></tr><tr><td>105</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Claim/Factory.php</td></tr><tr><td>106</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Claim/EqualsTo.php</td></tr><tr><td>107</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Claim/Basic.php</td></tr><tr><td>108</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Claim.php</td></tr><tr><td>109</th><td>/var/www/clients/client2/web16/private/vendor/lcobucci/jwt/src/Claim/Validatable.php</td></tr></table></div><div id="error-tabs-5"><table cellspacing="0" align="center" class="superglobal-detail"><tr><th colspan="2">Memory</th></tr><tr><td>Usage</td><td>4194304</td></tr></table></div></div><script type="text/javascript" src="//static.phalconphp.com/www/debug/3.0.x/bower_components/jquery/dist/jquery.min.js"></script><script type="text/javascript" src="//static.phalconphp.com/www/debug/3.0.x/bower_components/jquery-ui/jquery-ui.min.js"></script><script type="text/javascript" src="//static.phalconphp.com/www/debug/3.0.x/bower_components/jquery.scrollTo/jquery.scrollTo.min.js"></script><script type="text/javascript" src="//static.phalconphp.com/www/debug/3.0.x/prettify/prettify.js"></script><script type="text/javascript" src="//static.phalconphp.com/www/debug/3.0.x/pretty.js"></script></div></body></html>