1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\Modules\RocketChatWebclient;
9:
10: use Aurora\Api;
11: use Aurora\Modules\Contacts\Enums\StorageType;
12: use Aurora\Modules\Contacts\Module as ContactsModule;
13: use Aurora\Modules\Core\Module as CoreModule;
14: use Aurora\System\Enums\UserRole;
15: use Aurora\System\Exceptions\ApiException;
16: use Aurora\System\Utils;
17: use GuzzleHttp\Client;
18: use GuzzleHttp\Exception\ConnectException;
19: use GuzzleHttp\HandlerStack;
20: use GuzzleHttp\MessageFormatter;
21: use GuzzleHttp\Middleware;
22: use Monolog\Handler\RotatingFileHandler;
23: use Monolog\Logger;
24:
25: /**
26: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
27: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
28: * @copyright Copyright (c) 2023, Afterlogic Corp.
29: *
30: * @property Settings $oModuleSettings
31: *
32: * @package Modules
33: */
34: class Module extends \Aurora\System\Module\AbstractModule
35: {
36: public $oRocketChatSettingsManager = null;
37:
38: protected $sChatUrl = "";
39:
40: protected $sDemoPass = "demo";
41:
42: /**
43: * @var \GuzzleHttp\Client
44: */
45: protected $client = null;
46:
47: protected $adminAccount = null;
48:
49: protected $stack = null;
50:
51: protected $curUserData = false;
52:
53: protected $curUserInfo = false;
54:
55: /**
56: * @return Module
57: */
58: public static function getInstance()
59: {
60: return parent::getInstance();
61: }
62:
63: /**
64: * @return Module
65: */
66: public static function Decorator()
67: {
68: return parent::Decorator();
69: }
70:
71: /**
72: * @return Settings
73: */
74: public function getModuleSettings()
75: {
76: return $this->oModuleSettings;
77: }
78:
79: public function init()
80: {
81: $this->initLogging();
82:
83: $this->oRocketChatSettingsManager = new Managers\RocketChatSettings\Manager($this);
84:
85: $this->initConfig();
86:
87: $this->AddEntry('chat', 'EntryChat');
88: $this->AddEntry('chat-direct', 'EntryChatDirect');
89:
90: $this->subscribeEvent('Core::DeleteUser::before', array($this, 'onBeforeDeleteUser'));
91: $this->subscribeEvent('Core::Logout::after', array($this, 'onAfterLogout'));
92: }
93:
94: /**
95: * Obtains list of module settings for authenticated user.
96: *
97: * @param int|null $TenantId Tenant ID
98: *
99: * @return array
100: */
101: public function GetSettings($TenantId = null)
102: {
103: $aResult = [];
104: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
105:
106: $oUser = \Aurora\System\Api::getAuthenticatedUser();
107: if ($oUser) {
108: $sChatUrl = '';
109: $sAdminUsername = '';
110:
111: $oSettings = $this->oModuleSettings;
112: if (!empty($TenantId)) {
113: Api::checkUserRoleIsAtLeast(UserRole::TenantAdmin);
114: $oTenant = Api::getTenantById($TenantId);
115:
116: if ($oTenant && ($oUser->isAdmin() || $oUser->IdTenant === $oTenant->Id)) {
117: $sChatUrl = $oSettings->GetTenantValue($oTenant->Name, 'ChatUrl', $sChatUrl);
118: $sAdminUsername = $oSettings->GetTenantValue($oTenant->Name, 'AdminUsername', $sAdminUsername);
119: }
120: } else {
121: $sChatUrl = $oSettings->ChatUrl;
122: $sAdminUsername = $oSettings->AdminUsername;
123: }
124:
125: if ($oUser->isNormalOrTenant()) {
126: $aResult = [
127: 'ChatUrl' => $sChatUrl,
128: 'AllowAddMeetingLinkToEvent' => $this->oModuleSettings->AllowAddMeetingLinkToEvent,
129: 'MeetingLinkUrl' => $this->oModuleSettings->MeetingLinkUrl
130: ];
131:
132: } elseif ($oUser->isAdmin()) {
133: $aResult = [
134: 'ChatUrl' => $sChatUrl,
135: 'AdminUsername' => $sAdminUsername
136: ];
137: }
138: }
139:
140: return $aResult;
141: }
142:
143: /**
144: * Updates settings of the Chat Module.
145: *
146: * @param int $TenantId
147: * @param string $ChatUrl
148: * @param string $AdminUsername
149: * @param string $AdminPassword
150: * @return boolean
151: */
152: public function UpdateSettings($TenantId, $ChatUrl, $AdminUsername, $AdminPassword = null)
153: {
154: $oSettings = $this->oModuleSettings;
155: if (!empty($TenantId)) {
156: Api::checkUserRoleIsAtLeast(UserRole::TenantAdmin);
157: $oTenant = Api::getTenantById($TenantId);
158: $oUser = Api::getAuthenticatedUser();
159:
160: if ($oTenant && ($oUser->isAdmin() || $oUser->IdTenant === $oTenant->Id)) {
161: $aValues = [
162: 'ChatUrl' => $ChatUrl,
163: 'AdminUsername' => $AdminUsername
164: ];
165:
166: if ($AdminPassword) {
167: $sDecryptedPassword = Utils::DecryptValue($AdminPassword);
168: // checks that password is not encrypted already
169: if ($sDecryptedPassword === false) {
170: $aValues['AdminPassword'] = Utils::EncryptValue($AdminPassword);
171: } else {
172: $aValues['AdminPassword'] = $AdminPassword;
173: }
174: }
175:
176: return $oSettings->SaveTenantSettings($oTenant->Name, $aValues);
177: }
178: } else {
179: Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
180:
181: $oSettings->ChatUrl = $ChatUrl;
182: $oSettings->AdminUsername = $AdminUsername;
183:
184: if ($AdminPassword) {
185: $sDecryptedPassword = Utils::DecryptValue($AdminPassword);
186: // checks that password is not encrypted already
187: if ($sDecryptedPassword === false) {
188: $oSettings->AdminPassword = Utils::EncryptValue($AdminPassword);
189: } else {
190: $oSettings->AdminPassword = $AdminPassword;
191: }
192: }
193:
194: return $oSettings->Save();
195: }
196:
197: return false;
198: }
199:
200: /**
201: * This method returns the set of RocketChat settings that should havep artucular values.
202: *
203: * @param int|null $TenantId
204: * @return array
205: */
206: public function GetRocketChatSettings($TenantId = null)
207: {
208: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
209:
210: return $this->oRocketChatSettingsManager->get(
211: $this->getClient($TenantId),
212: $this->getAdminHeaders($TenantId)
213: );
214: }
215:
216: /**
217: * Checks connection to RocketChat with provided credentials. Uses saved password if password argument is ommitted.
218: *
219: * @param int|null $TenantId
220: * @param string $ChatUrl
221: * @param string $AdminUsername
222: * @param string $AdminPassword
223: * @return boolean
224: */
225: public function TestConnection($TenantId, $ChatUrl, $AdminUsername, $AdminPassword = null)
226: {
227: return false;
228: }
229:
230: /**
231: * Applies some settings to RocketChat to achive better integration with Aurora
232: *
233: * @param int|null $TenantId
234: * @return bool
235: */
236: public function ApplyRocketChatRequiredChanges($TenantId = null)
237: {
238: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
239:
240: return $this->oRocketChatSettingsManager->setConfigs(
241: $this->getClient($TenantId),
242: $this->getAdminHeaders($TenantId)
243: );
244: }
245:
246: /**
247: * Applies some text changes to RocketChat like user's home page.
248: *
249: * @param int|null $TenantId
250: * @return bool
251: */
252: public function ApplyRocketChatTextChanges($TenantId = null)
253: {
254: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
255:
256: return $this->oRocketChatSettingsManager->setTexts(
257: $this->getClient($TenantId),
258: $this->getAdminHeaders($TenantId)
259: );
260: }
261:
262: /**
263: * Applies css theme tweeks to match RocketChat custome theme to Aurora's one
264: *
265: * @param int|null $TenantId
266: * @return bool
267: */
268: public function ApplyRocketChatCssChanges($TenantId = null)
269: {
270: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
271:
272: return $this->oRocketChatSettingsManager->setCss(
273: $this->getClient($TenantId),
274: $this->getAdminHeaders($TenantId)
275: );
276: }
277:
278: /**
279: * This method handles direct chat entry point which can be used to start personal chat with a contact.
280: */
281: public function EntryChatDirect()
282: {
283: try {
284: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
285:
286: $sContactUuid = $this->oHttp->GetQuery('chat-direct');
287: $oCurrentUser = Api::getAuthenticatedUser();
288: $oContact = ContactsModule::Decorator()->GetContact($sContactUuid, $oCurrentUser->Id);
289: $sUserPublicId = null;
290: if ($oContact) {
291: if ($oContact->Storage === StorageType::Team) {
292: $sUserPublicId = $oContact->ViewEmail;
293: } else {
294: $oUser = $oContact ? Api::getUserById($oContact->IdUser) : null;
295: if ($oUser && $oCurrentUser && $oCurrentUser->IdTenant === $oUser->IdTenant) {
296: $sUserPublicId = $oUser->PublicId;
297: }
298: }
299: }
300: if ($sUserPublicId) {
301: $sChatLogin = $this->GetLoginForEmail($sUserPublicId);
302:
303: if ($sChatLogin) {
304: $this->showChat('direct/' . $sChatLogin . '?layout=embedded');
305: } else {
306: $this->showError('User not found');
307: }
308: } else {
309: $this->showError('User not found');
310: }
311: } catch (ApiException $oEx) {
312: $this->showError('User not found');
313: }
314: }
315:
316: /**
317: * This method handles direct chat entry point which is used to open RocketChat UI in a separate window.
318: */
319: public function EntryChat()
320: {
321: $oIntegrator = \Aurora\System\Managers\Integrator::getInstance();
322: $this->showChat('', $oIntegrator->buildHeadersLink());
323: }
324:
325: /**
326: *
327: */
328: public function InitChat()
329: {
330: if (!$this->curUserData) {
331: $mResult = false;
332: $oUser = Api::getAuthenticatedUser();
333: if ($oUser && $this->client && $this->initUser()) {
334: $sAuthToken = isset($_COOKIE['RocketChatAuthToken']) ? $_COOKIE['RocketChatAuthToken'] : null;
335: $sUserId = isset($_COOKIE['RocketChatUserId']) ? $_COOKIE['RocketChatUserId'] : null;
336:
337: if ($sAuthToken !== null && $sUserId !== null) {
338: $sAuthToken = Utils::DecryptValue($sAuthToken);
339: Api::AddSecret($sAuthToken);
340: try {
341: $res = $this->client->get('me', [
342: 'headers' => [
343: "X-Auth-Token" => $sAuthToken,
344: "X-User-Id" => $sUserId,
345: ],
346: 'http_errors' => false
347: ]);
348: if ($res->getStatusCode() === 200) {
349: $body = \json_decode($res->getBody(), true);
350: $sLang = '';
351: if (isset($body->settings->preferences->language)) {
352: $sLang = $body->settings->preferences->language;
353: }
354: $sUserLang = Utils::ConvertLanguageNameToShort($oUser->Language);
355: if ($sUserLang !== $sLang) {
356: $this->updateLanguage($sUserId, $sAuthToken, $sUserLang);
357: }
358:
359: $mResult = true;
360: }
361: } catch (ConnectException $oException) {
362: }
363: }
364:
365: if (!$mResult) {
366: $currentUserInfo = $this->getCurrentUserInfo();
367: $mLoginResult = false;
368: if ($currentUserInfo) {
369: $mLoginResult = $this->loginCurrentUser();
370: if (!$mLoginResult && !$this->isDemoUser($oUser->PublicId)) {
371: if ($this->updateUserPassword($currentUserInfo)) {
372: $mLoginResult = $this->loginCurrentUser();
373: }
374: }
375: } elseif ($this->createCurrentUser() !== false) {
376: $mLoginResult = $this->loginCurrentUser();
377: }
378:
379: if ($mLoginResult && isset($mLoginResult->data)) {
380: $iAuthTokenCookieExpireTime = (int) CoreModule::getInstance()->oModuleSettings->AuthTokenCookieExpireTime;
381: $sSameSite = CoreModule::getInstance()->oModuleSettings->CookieSameSite;
382:
383: $sAuthToken = $mLoginResult->data->authToken;
384: $sUserId = $mLoginResult->data->userId;
385:
386: Api::setCookie(
387: 'RocketChatAuthToken',
388: Utils::EncryptValue($sAuthToken),
389: \strtotime('+' . $iAuthTokenCookieExpireTime . ' days'),
390: true,
391: $sSameSite
392: );
393:
394: Api::setCookie(
395: 'RocketChatUserId',
396: $sUserId,
397: \strtotime('+' . $iAuthTokenCookieExpireTime . ' days'),
398: true,
399: $sSameSite
400: );
401:
402: $oUser->save();
403: }
404: }
405:
406: if ($sAuthToken && $sUserId) {
407: $mResult = [
408: 'authToken' => $sAuthToken,
409: 'userId' => $sUserId,
410: 'unreadCounter' => $this->getUnreadCounter($sUserId, $sAuthToken)
411: ];
412: }
413: }
414:
415: $this->curUserData = $mResult;
416: }
417:
418: return $this->curUserData;
419: }
420:
421: /**
422: * Returns RocketChat user's login for currently logged in user.
423: */
424: public function GetLoginForCurrentUser()
425: {
426: $mResult = false;
427:
428: $oUser = Api::getAuthenticatedUser();
429: if ($oUser) {
430: $mResult = $this->getUserNameFromEmail($oUser->PublicId);
431: }
432:
433: return $mResult;
434: }
435:
436: /**
437: * This method allows to get RocketChat login for a provided email address.
438: * If RocketChat account is missing it will be created
439: *
440: * @param string $Email
441: * @return string|false
442: */
443: public function GetLoginForEmail($Email)
444: {
445: $mResult = false;
446: $oUserInfo = $this->getUserInfo($Email);
447: if (!$oUserInfo) {
448: $oUserInfo = $this->createUser($Email);
449: }
450: if ($oUserInfo && $oUserInfo->success) {
451: $mResult = $oUserInfo->user->username;
452: }
453:
454: return $mResult;
455: }
456:
457: /**
458: * Returns number of unread messages for currently logged in user.
459: *
460: * @return int
461: */
462: protected function getUnreadCounter($sUserId, $sToken)
463: {
464: $iResult = 0;
465: if ($this->client) {
466: try {
467: $res = $this->client->get('subscriptions.get', [
468: 'headers' => [
469: "X-Auth-Token" => $sToken,
470: "X-User-Id" => $sUserId,
471: ],
472: 'http_errors' => false
473: ]);
474: if ($res->getStatusCode() === 200) {
475: $aResponse = \json_decode($res->getBody(), true);
476: if (is_array($aResponse['update'])) {
477: foreach ($aResponse['update'] as $update) {
478: $iResult += $update['unread'];
479: }
480: }
481: }
482: } catch (ConnectException $oException) {
483: }
484: }
485:
486: return $iResult;
487: }
488:
489: /**
490: * Returns number of unread messages for a provided user.
491: *
492: * @param int|null $iTenantId
493: * @return \GuzzleHttp\Client|null
494: */
495: protected function getClient($iTenantId = null)
496: {
497: $mResult = null;
498: $oSettings = $this->oModuleSettings;
499: $sChatUrl = '';
500: if (isset($iTenantId)) {
501: $oTenant = Api::getTenantById($iTenantId);
502: if ($oTenant) {
503: $sChatUrl = $oSettings->GetTenantValue($oTenant->Name, 'ChatUrl', '');
504: }
505: } else {
506: $sChatUrl = $oSettings->ChatUrl;
507: if (isset($this->client)) {
508: return $this->client;
509: }
510: }
511: if (!empty($sChatUrl)) {
512: $mResult = new Client([
513: 'base_uri' => \rtrim($sChatUrl, '/') . '/api/v1/',
514: 'verify' => false,
515: 'handler' => $this->stack
516: ]);
517: }
518:
519: return $mResult;
520: }
521:
522: /**
523: * Initializes chat client
524: */
525: protected function initConfig()
526: {
527: $this->sChatUrl = $this->oModuleSettings->ChatUrl;
528: $sAdminUser = $this->oModuleSettings->AdminUsername;
529:
530: if (!empty($this->sChatUrl) && !empty($sAdminUser)) {
531: $this->client = new Client([
532: 'base_uri' => \rtrim($this->sChatUrl, '/') . '/api/v1/',
533: 'verify' => false,
534: 'handler' => $this->stack
535: ]);
536: }
537: }
538:
539: /**
540: * Checks if current user is a demo user
541: *
542: * @param string $sEmail
543: * @return bool
544: */
545: protected function isDemoUser($sEmail)
546: {
547: $bDemo = false;
548:
549: if (class_exists('\Aurora\Modules\DemoModePlugin\Module')) {
550: /** @var \Aurora\Modules\DemoModePlugin\Module $oDemoModePlugin */
551: $oDemoModePlugin = Api::GetModuleDecorator('DemoModePlugin');
552: $bDemo = $oDemoModePlugin && $oDemoModePlugin->CheckDemoUser($sEmail);
553: }
554:
555: return $bDemo;
556: }
557:
558: /**
559: * Initializes RocketChat logging
560: */
561: protected function initLogging()
562: {
563: if ($this->oModuleSettings->EnableLogging && !$this->stack) {
564: $stack = HandlerStack::create();
565: $oLogger = new Logger('rocketchat');
566: $handler = new RotatingFileHandler(Api::GetLogFileDir() . 'rocketchat-log.txt');
567: $oLogger->pushHandler($handler);
568: //set your own exception handler so that the exception caused by logging does not interrupt the logic of the main code
569: $oLogger->setExceptionHandler(function ($e) {
570: Api::LogException($e);
571: });
572: collect([
573: "REQUEST: {method} - {uri} - HTTP/{version} - {req_headers} - {req_body}",
574: "RESPONSE: {code} - {res_body}",
575: ])->each(function ($messageFormat) use ($stack, $oLogger) {
576: // We'll use unshift instead of push, to add the middleware to the bottom of the stack, not the top
577: $oLogger->pushProcessor(function ($record) {
578: $record['message'] = str_replace(Api::$aSecretWords, '*****', $record['message']);
579: $record['message'] = preg_replace(
580: [
581: '/(X-Auth-Token|X-2fa-code):(.+?\s)/i',
582: '/("bcrypt"):(.*?\})/i',
583: '/("authToken"):(.*?,)/i'
584: ],
585: '$1: ***** ',
586: $record['message']
587: );
588: return $record;
589: });
590:
591: $stack->unshift(Middleware::log(
592: $oLogger,
593: new MessageFormatter($messageFormat)
594: ));
595: });
596: $this->stack = $stack;
597: }
598: }
599:
600: /**
601: * Initializes RocketChat client
602: *
603: * @return bool
604: */
605: protected function initUser()
606: {
607: $bResult = true;
608: if (!$this->getCurrentUserInfo()) {
609: if (!$this->createCurrentUser()) {
610: $bResult = false;
611: }
612: }
613:
614: return $bResult;
615: }
616:
617: /**
618: * Outputs HTML for chat page
619: *
620: * @param string $sUrl
621: * @param string $sIntegratorLinks
622: *
623: * @return void
624: */
625: protected function showChat($sUrl = '', $sIntegratorLinks = '')
626: {
627: $aUser = $this->InitChat();
628: $sResult = \file_get_contents($this->GetPath() . '/templates/Chat.html');
629: if (\is_string($sResult)) {
630: echo strtr($sResult, [
631: '{{TOKEN}}' => $aUser ? $aUser['authToken'] : '',
632: '{{URL}}' => rtrim($this->sChatUrl, '/') . '/' . $sUrl,
633: '{{IntegratorLinks}}' => $sIntegratorLinks,
634: ]);
635: }
636: }
637:
638: /**
639: * Outputs text error
640: *
641: * @param string $sMessage
642: */
643: protected function showError($sMessage = '')
644: {
645: echo $sMessage;
646: }
647:
648: /**
649: * Calculates RocketChat username from user's email depending on module settings
650: *
651: * @param string $sEmail
652: * @return string|false
653: */
654: protected function getUserNameFromEmail($sEmail)
655: {
656: $mResult = false;
657: $iChatUsernameFormat = $this->oModuleSettings->ChatUsernameFormat;
658:
659: $aEmailParts = explode("@", $sEmail);
660: if (isset($aEmailParts[1])) {
661: $aDomainParts = explode(".", $aEmailParts[1]);
662: }
663:
664: if (isset($aEmailParts[0])) {
665: $mResult = $aEmailParts[0];
666: if (isset($aDomainParts[0]) && ($iChatUsernameFormat == \Aurora\Modules\RocketChatWebclient\Enums\UsernameFormat::UsernameAndDomain)) {
667: $mResult .= "." . $aDomainParts[0];
668: }
669: if (isset($aEmailParts[1]) && ($iChatUsernameFormat == \Aurora\Modules\RocketChatWebclient\Enums\UsernameFormat::UsernameAndFullDomainName)) {
670: $mResult .= "." . $aEmailParts[1];
671: }
672: }
673:
674: return $mResult;
675: }
676:
677: /**
678: * Returs an Rocket Chat admin credentionals
679: *
680: * @param int|null $TenantId
681: * @param bool $EncrypdedPassword
682: * @return array|false
683: */
684: protected function getAdminCredentials($TenantId = null, $EncrypdedPassword = true)
685: {
686: static $mResult = false;
687:
688: if (!$mResult) {
689: $adminCreds = [];
690: $oSettings = $this->oModuleSettings;
691: if (isset($TenantId)) {
692: $oTenant = Api::getTenantById($TenantId);
693: if ($oTenant) {
694: $adminCreds = [
695: 'AdminUser' => $oSettings->GetTenantValue($oTenant->Name, 'AdminUsername', ''),
696: 'AdminPass' => $oSettings->GetTenantValue($oTenant->Name, 'AdminPassword', '')
697: ];
698: }
699: } else {
700: $adminCreds = [
701: 'AdminUser' => $oSettings->AdminUsername,
702: 'AdminPass' => $oSettings->AdminPassword
703: ];
704: }
705: if (is_array($adminCreds) && isset($adminCreds['AdminPass'])) {
706: if ($EncrypdedPassword) {
707: $adminCreds['AdminPass'] = Utils::DecryptValue($adminCreds['AdminPass']);
708: }
709: if ($adminCreds['AdminPass']) {
710: Api::AddSecret($adminCreds['AdminPass']);
711: $mResult = $adminCreds;
712: }
713: }
714: }
715:
716: return $mResult;
717: }
718:
719: /**
720: * Obtains RocketChat token for admin user
721: *
722: * @param int $TenantId
723: * @param array $aAdminCreds
724: * @return object|false
725: */
726: protected function loginAdminAccount($TenantId, $aAdminCreds)
727: {
728: $mResult = false;
729:
730: $client = $this->getClient($TenantId);
731: try {
732: if ($client && $aAdminCreds) {
733: $res = $client->post('login', [
734: 'form_params' => [
735: 'user' => $aAdminCreds['AdminUser'],
736: 'password' => $aAdminCreds['AdminPass']
737: ],
738: 'http_errors' => false
739: ]);
740: if ($res->getStatusCode() === 200) {
741: $mResult = \json_decode($res->getBody());
742: }
743: }
744: } catch (ConnectException $oException) {
745: }
746:
747: return $mResult;
748: }
749:
750: /**
751: * @param int|null $TenantId
752: * @return object|false
753: */
754: protected function getAdminAccount($TenantId = null)
755: {
756: $mResult = false;
757:
758: if (!isset($TenantId) && isset($this->adminAccount)) {
759: return $this->adminAccount;
760: }
761:
762: $aAdminCreds = $this->getAdminCredentials($TenantId);
763: if ($aAdminCreds) {
764: $mResult = $this->loginAdminAccount($TenantId, $aAdminCreds);
765: }
766: if (!$mResult) {
767: $aAdminCreds = $this->getAdminCredentials($TenantId, false);
768: $mResult = $this->loginAdminAccount($TenantId, $aAdminCreds);
769: }
770: if (!isset($TenantId) && $mResult) {
771: $this->adminAccount = $mResult;
772: }
773:
774: return $mResult;
775: }
776:
777: /**
778: * @param int|null $TenantId
779: * @return array
780: */
781: protected function getAdminHeaders($TenantId = null)
782: {
783: $oAdmin = $this->getAdminAccount($TenantId);
784: $aAdminCreds = $this->getAdminCredentials($TenantId);
785: if ($oAdmin && $aAdminCreds) {
786: return [
787: 'X-Auth-Token' => $oAdmin->data->authToken,
788: 'X-User-Id' => $oAdmin->data->userId,
789: 'X-2fa-code' => hash('sha256', $aAdminCreds['AdminPass']),
790: 'X-2fa-method' => 'password'
791: ];
792: }
793: return [];
794: }
795:
796: /**
797: * Returns RocketChat user info
798: *
799: * @param int $sEmail
800: * @return object|false
801: */
802: protected function getUserInfo($sEmail)
803: {
804: $mResult = false;
805: $sUserName = $this->getUserNameFromEmail($sEmail);
806: try {
807: if ($this->client) {
808: $res = $this->client->get('users.info', [
809: 'query' => [
810: 'username' => $sUserName
811: ],
812: 'headers' => $this->getAdminHeaders(),
813: 'http_errors' => false
814: ]);
815: if ($res->getStatusCode() === 200) {
816: $mResult = \json_decode($res->getBody());
817: }
818: }
819: } catch (ConnectException $oException) {
820: \Aurora\System\Api::Log('Cannot get ' . $sUserName . ' user info. Exception is below.');
821: \Aurora\System\Api::LogException($oException);
822: }
823:
824: return $mResult;
825: }
826:
827: /**
828: *
829: */
830: protected function getCurrentUserInfo()
831: {
832: if (!$this->curUserInfo) {
833: $oUser = Api::getAuthenticatedUser();
834: if ($oUser) {
835: $this->curUserInfo = $this->getUserInfo($oUser->PublicId);
836: }
837: }
838:
839: return $this->curUserInfo;
840: }
841:
842: /**
843: * Creates an user account in RocketChat
844: *
845: * @param string $sEmail
846: * @return object|false
847: */
848: protected function createUser($sEmail)
849: {
850: $mResult = false;
851:
852: $oAccount = CoreModule::Decorator()->GetAccountUsedToAuthorize($sEmail);
853: if ($oAccount && $this->client) {
854: if (!$this->isDemoUser($sEmail)) {
855: $sPassword = $oAccount->getPassword();
856: } else {
857: $sPassword = $this->sDemoPass;
858: }
859:
860: Api::AddSecret($sPassword);
861:
862: $sLogin = $this->getUserNameFromEmail($sEmail);
863: $sName = isset($oAccount->FriendlyName) && $oAccount->FriendlyName !== '' ? $oAccount->FriendlyName : $sLogin;
864: try {
865: $res = $this->client->post('users.create', [
866: 'json' => [
867: 'email' => $sEmail,
868: 'name' => $sName,
869: 'password' => $sPassword,
870: 'username' => $sLogin
871: ],
872: 'headers' => $this->getAdminHeaders(),
873: 'http_errors' => false
874: ]);
875: if ($res->getStatusCode() === 200) {
876: $mResult = \json_decode($res->getBody());
877: }
878: } catch (ConnectException $oException) {
879: \Aurora\System\Api::Log('Cannot create ' . $sEmail . ' user. Exception is below.');
880: \Aurora\System\Api::LogException($oException);
881: }
882: }
883: return $mResult;
884: }
885:
886: protected function createCurrentUser()
887: {
888: $mResult = false;
889:
890: $oUser = Api::getAuthenticatedUser();
891:
892: if ($oUser) {
893: $mResult = $this->createUser($oUser->PublicId);
894: }
895:
896: return $mResult;
897: }
898:
899: protected function loginCurrentUser()
900: {
901: $mResult = false;
902:
903: $oUser = Api::getAuthenticatedUser();
904: if ($oUser) {
905: $oAccount = CoreModule::Decorator()->GetAccountUsedToAuthorize($oUser->PublicId);
906: if ($oAccount && $this->client) {
907: if (!$this->isDemoUser($oUser->PublicId)) {
908: $sPassword = $oAccount->getPassword();
909: } else {
910: $sPassword = $this->sDemoPass;
911: }
912: Api::AddSecret($sPassword);
913: try {
914: $res = $this->client->post('login', [
915: 'form_params' => [
916: 'user' => $this->getUserNameFromEmail($oUser->PublicId),
917: 'password' => $sPassword
918: ],
919: 'http_errors' => false
920: ]);
921: if ($res->getStatusCode() === 200) {
922: $mResult = \json_decode($res->getBody());
923: $sLang = '';
924: if (isset($mResult->data->me->settings->preferences->language)) {
925: $sLang = $mResult->data->me->settings->preferences->language;
926: }
927: $sUserLang = Utils::ConvertLanguageNameToShort($oUser->Language);
928: if ($sUserLang !== $sLang) {
929: $this->updateLanguage($mResult->data->userId, $mResult->data->authToken, $sUserLang);
930: }
931: }
932: } catch (ConnectException $oException) {
933: }
934: }
935: }
936:
937: return $mResult;
938: }
939:
940: protected function logout()
941: {
942: $mResult = false;
943:
944: if ($this->client) {
945: try {
946: $sAuthToken = isset($_COOKIE['RocketChatAuthToken']) ? $_COOKIE['RocketChatAuthToken'] : null;
947: $sUserId = isset($_COOKIE['RocketChatUserId']) ? $_COOKIE['RocketChatUserId'] : null;
948: if ($sAuthToken !== null && $sUserId !== null) {
949: $res = $this->client->post('logout', [
950: 'headers' => [
951: "X-Auth-Token" => $sAuthToken,
952: "X-User-Id" => $sUserId,
953: ],
954: 'http_errors' => false
955: ]);
956: if ($res->getStatusCode() === 200) {
957: $mResult = \json_decode($res->getBody());
958: }
959: }
960: } catch (ConnectException $oException) {
961: Api::LogException($oException);
962: }
963: }
964:
965: return $mResult;
966: }
967:
968: protected function updateLanguage($sUserId, $sToken, $sLang)
969: {
970: if ($this->client) {
971: $this->client->post('users.setPreferences', [
972: 'json' => [
973: 'userId' => $sUserId,
974: 'data' => [
975: "language" => $sLang
976: ]
977: ],
978: 'headers' => [
979: "X-Auth-Token" => $sToken,
980: "X-User-Id" => $sUserId
981: ],
982: 'http_errors' => false
983: ]);
984: }
985: }
986:
987: protected function updateUserPassword($userInfo)
988: {
989: $mResult = false;
990: $oUser = Api::getAuthenticatedUser();
991: if ($oUser) {
992: $oAccount = CoreModule::Decorator()->GetAccountUsedToAuthorize($oUser->PublicId);
993: if ($oAccount && $this->client) {
994: Api::AddSecret($oAccount->getPassword());
995: $res = $this->client->post('users.update', [
996: 'json' => [
997: 'userId' => $userInfo->user->_id,
998: 'data' => [
999: 'password' => $oAccount->getPassword()
1000: ]
1001: ],
1002: 'headers' => $this->getAdminHeaders(),
1003: 'http_errors' => false
1004: ]);
1005: $mResult = ($res->getStatusCode() === 200);
1006: }
1007: }
1008:
1009: return $mResult;
1010: }
1011:
1012: public function onBeforeDeleteUser(&$aArgs, &$mResult)
1013: {
1014: $client = null;
1015: $adminHeaders = null;
1016: $oAuthenticatedUser = Api::getAuthenticatedUser();
1017: if ($oAuthenticatedUser && $oAuthenticatedUser->isNormalOrTenant() &&
1018: $oAuthenticatedUser->Id === (int) $aArgs['UserId']
1019: ) {
1020: // Access is granted
1021: $client = $this->client;
1022: $adminHeaders = $this->getAdminHeaders();
1023: } else {
1024: $oUser = Api::getUserById((int) $aArgs['UserId']);
1025: if ($oUser) {
1026: if ($oAuthenticatedUser->Role === UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
1027: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::TenantAdmin);
1028: } elseif ($oAuthenticatedUser->Role === UserRole::SuperAdmin) {
1029: \Aurora\System\Api::checkUserRoleIsAtLeast(UserRole::SuperAdmin);
1030: }
1031:
1032: $client = $this->getClient($oUser->IdTenant);
1033: $adminHeaders = $this->getAdminHeaders($oUser->IdTenant);
1034: }
1035: }
1036:
1037: $sUserName = $this->getUserNameFromEmail(Api::getUserPublicIdById($aArgs['UserId']));
1038: try {
1039: if ($client) {
1040: $oRes = $client->post('users.delete', [
1041: 'json' => [
1042: 'username' => $sUserName
1043: ],
1044: 'headers' => $adminHeaders,
1045: 'http_errors' => false,
1046: 'timeout' => 1
1047: ]);
1048: if ($oRes->getStatusCode() === 200) {
1049: $mResult = true;
1050: }
1051: }
1052: } catch (ConnectException $oException) {
1053: \Aurora\System\Api::Log('Cannot delete ' . $sUserName . ' user from RocketChat. Exception is below.');
1054: \Aurora\System\Api::LogException($oException);
1055: }
1056: }
1057:
1058: public function onAfterLogout($aArgs, &$mResult)
1059: {
1060: $sSameSite = CoreModule::getInstance()->oModuleSettings->CookieSameSite;
1061:
1062: \Aurora\System\Api::setCookie(
1063: 'RocketChatAuthToken',
1064: '',
1065: -1,
1066: true,
1067: $sSameSite
1068: );
1069: \Aurora\System\Api::setCookie(
1070: 'RocketChatUserId',
1071: '',
1072: -1,
1073: true,
1074: $sSameSite
1075: );
1076: }
1077: }
1078: