Rev 769 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?phpdeclare(strict_types = 1);namespace LeadersLinked;use Laminas\Db\Adapter\AdapterInterface;use Laminas\ModuleManager\ModuleEvent;use Laminas\ModuleManager\ModuleManager;use Laminas\Mvc\MvcEvent;use Laminas\Config\Reader\Ini;use Laminas\Permissions\Acl\Acl;use Laminas\Permissions\Acl\Role\GenericRole;use LeadersLinked\Plugin\CurrentUserPlugin;use LeadersLinked\Mapper\UserMapper;use LeadersLinked\Authentication\AuthTokenAdapter;use Laminas\Authentication\AuthenticationService;use Laminas\Permissions\Acl\Resource\GenericResource;use LeadersLinked\Model\UserType;use LeadersLinked\Plugin\CurrentNetworkPlugin;use LeadersLinked\Model\Network;use LeadersLinked\Model\User;use LeadersLinked\Mapper\CompanyUserMapper;use LeadersLinked\Model\CompanyUser;use LeadersLinked\Mapper\CompanyMapper;use LeadersLinked\Mapper\CompanyServiceMapper;use LeadersLinked\Model\Service;use LeadersLinked\Library\Functions;use LeadersLinked\Mapper\DailyPulseMapper;use LeadersLinked\Model\DailyPulse;use LeadersLinked\Mapper\OrganizationPositionMapper;use LeadersLinked\Mapper\KnowledgeAreaCategoryJobDescriptionMapper;use LeadersLinked\Mapper\MyCoachCategoryJobDescriptionMapper;use LeadersLinked\Mapper\KnowledgeAreaCategoryUserMapper;use LeadersLinked\Mapper\MyCoachCategoryUserMapper;use Firebase\JWT\JWT;use Firebase\JWT\Key;use LeadersLinked\Mapper\JwtTokenMapper;use LeadersLinked\Authentication\AuthUserIdAdapter;use LeadersLinked\Model\JwtToken;use LeadersLinked\Mapper\MicrolearningTopicUserMapper;use Laminas\Http\Response;class Module{/**** @var Acl*/private $acl;/**** @var AdapterInterface*/private $adapter;/**** @var CurrentUserPlugin*/private $currentUserPlugin;/**** @var CurrentNetworkPlugin*/private $currentNetworkPlugin;/**** @var array*/private $routesAuthorized = [];/**** @var boolean*/private $authByOTP = false;/**** @var boolean*/private $authByJWT = false;/**** @var int*/private $jwtID = 0;/**** @var JwtToken*/private $jwtToken;/**** @var array*/private $config;public function init(ModuleManager $moduleManager){$events = $moduleManager->getEventManager();$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this,'onMergeConfig'));}public function onMergeConfig(ModuleEvent $event){$configListener = $event->getConfigListener();$this->config = $configListener->getMergedConfig(false);$reader = new Ini();$data = $reader->fromFile('config/leaderslinked.ini');$prefix = 'leaderslinked';foreach ($data as $section => $pairs) {foreach ($pairs as $key => $value) {$this->config[$prefix . '.' . $section . '.' . $key] = $value;}}$configListener->setMergedConfig($this->config);}public function getConfig(): array{return include __DIR__ . '/../config/module.config.php';}public function onBootstrap(MvcEvent $event){$timezone = $this->config['leaderslinked.runmode.timezone'];date_default_timezone_set($timezone);$response = $event->getResponse();$request = $event->getRequest();$serviceManager = $event->getApplication()->getServiceManager();// --- Configuración CORS con whitelist ---$whitelistString = $this->config['leaderslinked.cors.allowed_origins'] ?? '';$allowedOrigins = array_map('trim', explode(',', $whitelistString));$headers = $request->getHeaders();$originHeader = $headers->get('Origin');if ($originHeader) {$origin = $originHeader->getFieldValue();error_log("CORS Check - Origen de la solicitud: " . $origin);error_log("CORS Check - Whitelist cargada: " . implode(', ', $allowedOrigins));if (in_array($origin, $allowedOrigins)) {$responseHeaders = $response->getHeaders();$responseHeaders->addHeaderLine('Access-Control-Allow-Origin', $origin);$responseHeaders->addHeaderLine('Access-Control-Allow-Credentials', 'true');$responseHeaders->addHeaderLine('Access-Control-Allow-Headers', 'Authorization, Content-Type, token, secret, rand, created, x-csrf-token');$responseHeaders->addHeaderLine('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE, PATCH');$responseHeaders->addHeaderLine('Access-Control-Max-Age', '86400');error_log("CORS Check - Headers añadidos correctamente para: " . $origin);} else {error_log("CORS Check - Origen NO permitido: " . $origin);}}// Manejar solicitudes OPTIONS (preflight)if ($request->isOptions()) {error_log("CORS Check - Solicitud OPTIONS detectada, enviando respuesta 200");$response->setStatusCode(200);$event->setResponse($response);return $response;}$event->setResponse($response);$eventManager = $event->getApplication()->getEventManager();$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this,'onDispatchError'], 0);$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, [$this,'onRenderError'], 0);$adapter = $serviceManager->get('leaders-linked-db');/** $session = $serviceManager->get('leaders-linked-session');* $session->start();* $session->regenerateId(true);*/$translator = $serviceManager->get('MvcTranslator');$translator->addTranslationFile('phpArray', __DIR__ . '/i18n/validate.php', 'default');$translator->addTranslationFile('phpArray', __DIR__ . '/i18n/spanish.php', 'default');\Laminas\Validator\AbstractValidator::setDefaultTranslator($translator);$headers = $event->getRequest()->getHeaders();if ($headers->has('token')) {$device_uuid = Functions::sanitizeFilterString($headers->get('token')->getFieldValue());} else {$device_uuid = '';}if ($headers->has('secret')) {$password = Functions::sanitizeFilterString($headers->get('secret')->getFieldValue());} else {$password = '';}if ($headers->has('rand')) {$rand = Functions::sanitizeFilterString($headers->get('rand')->getFieldValue());} else {$rand = 0;}if ($headers->has('created')) {$timestamp = Functions::sanitizeFilterString($headers->get('created')->getFieldValue());} else {$timestamp = 0;}$this->currentNetworkPlugin = CurrentNetworkPlugin::getInstance($adapter);if (!$this->currentNetworkPlugin->hasNetwork()) {$this->currentNetworkPlugin->fetchDefaultNetwork();}if (!$this->currentNetworkPlugin->hasNetwork()) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => '200 Unauthorized - Private network - not found','fatal' => true]);$this->sendResponse($response, $code, $content);}if ($this->currentNetworkPlugin->getNetwork()->status == Network::STATUS_INACTIVE) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => '200 Unauthorized - Private network - inactive','fatal' => true]);$this->sendResponse($response, $code, $content);}$this->authByOTP = false;if ($device_uuid && $password && $rand && $timestamp) {$this->authByOTP = true;$tokenAuthAdapter = new AuthTokenAdapter($adapter);$tokenAuthAdapter->setData($device_uuid, $password, $timestamp, $rand);$authService = new AuthenticationService();$result = $authService->authenticate($tokenAuthAdapter);if ($result->getCode() != \Laminas\Authentication\Result::SUCCESS) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => $result->getMessages()[0],'fatal' => true]);$this->sendResponse($response, $code, $content);}}$this->jwtID = 0;$this->authByJWT = false;$headers = getallheaders();if (! empty($headers['authorization']) || ! empty($headers['Authorization'])) {$token = trim(empty($headers['authorization']) ? $headers['Authorization'] : $headers['authorization']);if (substr($token, 0, 6) == 'Bearer') {$token = trim(substr($token, 7));if (! empty($this->config['leaderslinked.jwt.key'])) {$key = $this->config['leaderslinked.jwt.key'];try {$payload = JWT::decode($token, new Key($key, 'HS256'));if (empty($payload->iss) || $payload->iss != $_SERVER['HTTP_HOST']) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => 'Unauthorized - JWT - Wrong server','fatal' => true]);$this->sendResponse($response, $code, $content);}$uuid = empty($payload->uuid) ? '' : $payload->uuid;if ($uuid) {$jwtTokenMapper = JwtTokenMapper::getInstance($adapter);$jwtToken = $jwtTokenMapper->fetchOneByUuid($uuid);if ($jwtToken) {$this->jwtID = $jwtToken->id;$_SESSION['aes'] = $jwtToken->aes;if ($jwtToken->user_id) {$authByUserId = new AuthUserIdAdapter($adapter);$authByUserId->setData($jwtToken->user_id);$authService = new AuthenticationService();$result = $authService->authenticate($authByUserId);if ($result->getCode() != \Laminas\Authentication\Result::SUCCESS) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => $result->getMessages()[0],'fatal' => true]);$this->sendResponse($response, $code, $content);}}}else {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => 'Unauthorized - JWT - Expired','fatal' => true]);$this->sendResponse($response, $code, $content);}}} catch (\Exception $e) {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => 'Unauthorized - JWT - Wrong key','fatal' => true]);$this->sendResponse($response, $code, $content);}}}}if (empty($_SERVER['REDIRECT_URL'])) {if (empty($_SERVER['REQUEST_URI'])) {$routeName = '';} else {$routeName = $_SERVER['REQUEST_URI'];}} else {$routeName = $_SERVER['REDIRECT_URL'];}$routeName = strtolower(trim($routeName));if (strlen($routeName) > 0 && substr($routeName, 0, 1) == '/') {$routeName = substr($routeName, 1);}$this->currentUserPlugin = CurrentUserPlugin::getInstance($adapter);if ($this->currentUserPlugin->hasIdentity()) {if (User::STATUS_BANNED == $this->currentUserPlugin->getUser()->status) {$code = 200;$content = json_encode(['success' => false,'data' => '403 Forbidden - Banned','fatal' => true]);$this->sendResponse($response, $code, $content);}}if ($this->authByOTP && substr($routeName, 0, 8) == 'services') {$checkUserForNetwork = false;} else {if ($this->currentUserPlugin->hasIdentity()) {$checkUserForNetwork = true;} else {$checkUserForNetwork = false;}}if ($checkUserForNetwork) {if (! $routeName || in_array($routeName, ['signout','signin','home'])) {$checkUserForNetwork = false;}}if ($checkUserForNetwork) {if ($this->currentUserPlugin->getUser()->network_id != $this->currentNetworkPlugin->getNetworkId()) {$response = $event->getResponse();$content = json_encode(['success' => false,'data' => '200 Unauthorized - The user is not part of this private network','fatal' => true]);$this->sendResponse($response, 200, $content);exit();}}$this->initAcl($event);$sharedManager = $eventManager->getSharedManager();$sharedManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, [$this,'authPreDispatch'], 100);$sharedManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, [$this,'authPosDispatch'], - 100);}public function initAcl(MvcEvent $event){$serviceManager = $event->getApplication()->getServiceManager();$adapter = $serviceManager->get('leaders-linked-db');require_once (dirname(__DIR__) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'acl.config.php');$this->acl = new Acl();$resources = getAclResources();foreach ($resources as $resourceName) {$this->acl->addResource(new GenericResource($resourceName));}$usertypes = getAclUsertype();foreach ($usertypes as $usertype => $resources) {$this->acl->addRole(new GenericRole($usertype));foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($this->currentUserPlugin->hasIdentity()) {$user_id = $this->currentUserPlugin->getUserId();if ($this->currentUserPlugin->getUser()->is_super_user == User::IS_SUPER_USER_YES) {$resources = getAclSuperAdmin();foreach ($resources as $resourceName) {$this->acl->allow(UserType::ADMIN, $resourceName);}}} else {$user_id = 0;}$allowMyCoach = false;$allowKnowledgeArea = false;$allowDailyPulse = false;if ($user_id) {$allowMicrolearning = $this->isMicroLeargningAccessGranted($adapter, $user_id);$allowHabit = $this->isHabitsAccessGranted($adapter, $user_id);} else {$allowMicrolearning = false;$allowHabit = false;}$companyMapper = CompanyMapper::getInstance($adapter);$company = $companyMapper->fetchDefaultForNetworkByNetworkId($this->currentNetworkPlugin->getNetwork()->id);if ($company) {$companyServiceMapper = CompanyServiceMapper::getInstance($adapter);$companyService = $companyServiceMapper->fetchOneActiveByCompanyIdAndServiceId($company->id, Service::DAILY_PULSE);$companyUserMapper = CompanyUserMapper::getInstance($adapter);$companyUser = $companyUserMapper->fetchOneAcceptedByCompanyIdAndUserId($company->id, $this->currentUserPlugin->getUserId());if ($companyService) {$dailyPulseMapper = DailyPulseMapper::getInstance($adapter);$dailyPulse = $dailyPulseMapper->fetchOneByCompanyId($company->id);if ($dailyPulse) {$privacy = $dailyPulse->privacy;} else {$privacy = DailyPulse::PRIVACY_COMPANY;}if ($privacy == DailyPulse::PRIVACY_PUBLIC) {$allowDailyPulse = true;} else {$allowDailyPulse = ! empty($companyUser);}}$job_description_ids = [];$organizationPositionMapper = OrganizationPositionMapper::getInstance($adapter);$records = $organizationPositionMapper->fetchAllByCompanyIdAndEmployeeId($company->id, $this->currentUserPlugin->getUserId());foreach ($records as $record) {array_push($job_description_ids, $record->job_description_id);}$companyService = $companyServiceMapper->fetchOneActiveByCompanyIdAndServiceId($company->id, Service::KNOWLEDGE_AREA);if ($companyService) {if ($job_description_ids) {$knowledgeAreaCategoryJobDescriptionMapper = KnowledgeAreaCategoryJobDescriptionMapper::getInstance($adapter);$records = $knowledgeAreaCategoryJobDescriptionMapper->fetchAllByCompanyIdAndJobDescriptionIds($company->id, $job_description_ids);if (! empty($records)) {$allowKnowledgeArea = true;}}if ($companyUser && ! $allowKnowledgeArea) {$knowledgeAreaCategoryUserMapper = KnowledgeAreaCategoryUserMapper::getInstance($adapter);$records = $knowledgeAreaCategoryUserMapper->fetchAllByUserId($companyUser->user_id);if (! empty($records)) {$allowKnowledgeArea = true;}}}$companyService = $companyServiceMapper->fetchOneActiveByCompanyIdAndServiceId($company->id, Service::MY_COACH);if ($companyService) {if ($job_description_ids) {$myCoachCategoryJobDescriptionMapper = MyCoachCategoryJobDescriptionMapper::getInstance($adapter);$records = $myCoachCategoryJobDescriptionMapper->fetchAllByCompanyIdAndJobDescriptionIds($company->id, $job_description_ids);if (! empty($records)) {$allowKnowledgeArea = true;}}if ($companyUser && ! $allowMyCoach) {$myCoachCategoryUserMapper = MyCoachCategoryUserMapper::getInstance($adapter);$records = $myCoachCategoryUserMapper->fetchAllByUserId($companyUser->user_id);if (! empty($records)) {$allowMyCoach = true;}}}} else {$companyUser = '';}$usertype = $this->currentUserPlugin->getUserTypeId();if ($allowDailyPulse) {$resources = getAclDailyPulse();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($allowMicrolearning) {$resources = getAclMicrolearning();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($allowHabit) {$resources = getAclHabits();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($allowKnowledgeArea) {$resources = getAclKnowledgeArea();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($allowMyCoach) {$resources = getAclMyCoach();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($this->currentNetworkPlugin->getNetwork()->default == Network::DEFAULT_YES) {$usertypes = getAclUsertypeDefaultNetwork();foreach ($usertypes as $usertype => $resources) {foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}} else {if ($this->currentUserPlugin->hasIdentity()) {if ($company) {if ($companyUser) {$usertype = $this->currentUserPlugin->getUserTypeId();if ($companyUser->creator == CompanyUser::CREATOR_YES) {$resources = getAclUsertypeOtherNetworkCreator();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}if ($companyUser->creator == CompanyUser::CREATOR_NO) {$resources = getAclUsertypeOtherNetworkNonCreator();foreach ($resources as $resourceName) {$this->acl->allow($usertype, $resourceName);}}}}}}$event->getViewModel()->setVariable('acl', $this->acl);}public function onDispatchError(MvcEvent $event){$this->processError($event);}public function onRenderError(MvcEvent $event){$this->processError($event);}/**** @param \Laminas\Http\Response $response* @param int $code* @param string $content*/public function sendResponse($response, $code, $content){$headers = $response->getHeaders();$headers->clearHeaders();$headers->addHeaderLine('Content-type', 'application/json; charset=UTF-8');Functions::addCrossSiteToResponse($response);$response->setStatusCode($code);$response->setContent($content); // json_encode($data));$response->send();exit();}public function processError(MvcEvent $event){$error = $event->getError();if (! $error) {return;}$response = $event->getResponse();if ('error-exception' == $error) {$exception = $event->getParam('exception');error_log($exception->getCode() . ' ' . $exception->getMessage());error_log($exception->getTraceAsString());$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => $exception->getCode() . ' ' . $exception->getMessage(),'fatal' => true]);$this->sendResponse($response, $code, $content);} else if ('error-router-no-match' == $error) {$response = $event->getResponse();$code = 404;$content = json_encode(['success' => false,'data' => 'error-router-no-match','fatal' => true]);$this->sendResponse($response, $code, $content);} else if (' error-controller-not-found' == $error) {$response = $event->getResponse();$code = 404;$content = json_encode(['success' => false,'data' => 'error-controller-not-found','fatal' => true]);$this->sendResponse($response, $code, $content);} else {$response = $event->getResponse();$code = 200;$content = json_encode(['success' => false,'data' => $error,'fatal' => true]);$this->sendResponse($response, $code, $content);}exit();}public function authPreDispatch(MvcEvent $event){$serviceManager = $event->getApplication()->getServiceManager();$adapter = $serviceManager->get('leaders-linked-db');$routeName = $event->getRouteMatch()->getMatchedRouteName();$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? trim(strtoupper($_SERVER['REQUEST_METHOD'])) : '';if ($requestMethod == 'POST') {if ($this->authByOTP && substr($routeName, 0, 8) == 'services') {$exclude = true;} else {$exclude = false;$usertypes = getAclUsertype();foreach ($usertypes[UserType::GUEST] as $resourceName) {if ($routeName == $resourceName) {$exclude = true;break;}}}if (! $exclude) {$httpToken = isset($_SERVER['HTTP_X_CSRF_TOKEN']) ? $_SERVER['HTTP_X_CSRF_TOKEN'] : '';if ($this->jwtID) {$jwtTokenMapper = JwtTokenMapper::getInstance($this->adapter);$jwtToken = $jwtTokenMapper->fetchOne($this->jwtID);if ($jwtToken) {$sessionToken = $jwtToken->csrf;// $jwtToken->csrf= '';// $jwtTokenMapper->update($jwtToken);} else {$sessionToken = '';}} else {$sessionToken = '';}// error_log('$this->jwtID = ' . $this->jwtID . ' $httpToken = ' . $httpToken . ' $sessionToken = ' . $sessionToken);// if ( $httpToken != $sessionToken) {// $response = $event->getResponse();// $this->sendResponse($response, 200, json_encode(['success' => false, 'data' => 'Unauthorized - CSRF fail', 'fatal' => true]));// }}}if ($this->currentUserPlugin->hasIdentity()) {$user = $this->currentUserPlugin->getUser();$user_id = $user->id;$userTypeId = $user->usertype_id;} else {$userTypeId = UserType::GUEST;$user_id = 0;}if ($this->acl->isAllowed($userTypeId, $routeName)) {$user = $this->currentUserPlugin->getUser();if ($user) {$updateLastActivity = true;if ('chat' == substr($routeName, 0, 4)) {$updateLastActivity = false;}if ('inmail' == substr($routeName, 0, 6)) {$updateLastActivity = false;}if ('check-session' == $routeName) {$updateLastActivity = false;}if ($updateLastActivity) {$userMapper = UserMapper::getInstance($adapter);$userMapper->updateLastActivity($user->id);}}} else {$response = $event->getResponse();$response->setStatusCode(200);$response->setContent(json_encode(['success' => false,'data' => 'Unauthorized - Does not have permission','fatal' => true]));$response->send();exit();}}public function authPosDispatch(MvcEvent $event){// $response = $event->getResponse();// Functions::addCrossSiteToResponse($response);}/**** @param \Laminas\Db\Adapter\AdapterInterface $adapter* @param int $user_id* @return boolean*/private function isMicroLeargningAccessGranted($adapter, $user_id){$accessGranted = false;$topicUserMapper = \LeadersLinked\Mapper\MicrolearningTopicUserMapper::getInstance($adapter);$now = $topicUserMapper->getDatebaseNow();$records = $topicUserMapper->fetchAllActiveByUserId($user_id);foreach ($records as $record) {if ($record->access != \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_UNLIMITED && $record->access != \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_PAY_PERIOD) {continue;}if ($record->access == \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_PAY_PERIOD) {if ($now < $record->paid_from || $now > $record->paid_to) {continue;}}$accessGranted = true;break;}return $accessGranted;}/**** @param \Laminas\Db\Adapter\AdapterInterface $adapter* @param int $user_id* @return boolean*/private function isHabitsAccessGranted($adapter, $user_id){$accessGranted = false;$habitUserMapper = \LeadersLinked\Mapper\HabitUserMapper::getInstance($adapter);$now = $habitUserMapper->getDatebaseNow();$records = $habitUserMapper->fetchAllActiveByUserId($user_id);foreach ($records as $record) {if ($record->access != \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_UNLIMITED && $record->access != \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_PAY_PERIOD) {continue;}if ($record->access == \LeadersLinked\Model\MicrolearningTopicUser::ACCESS_PAY_PERIOD) {if ($now < $record->paid_from || $now > $record->paid_to) {continue;}}$accessGranted = true;break;}return $accessGranted;}}