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\OpenPgpWebclient;
9:
10: use Aurora\Modules\Contacts\Enums\StorageType;
11: use Aurora\Modules\Contacts\Models\Contact;
12: use Aurora\System\Api;
13: use Aurora\System\Enums\UserRole;
14:
15: /**
16: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
17: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
18: * @copyright Copyright (c) 2023, Afterlogic Corp.
19: *
20: * @package Modules
21: */
22: class Module extends \Aurora\System\Module\AbstractWebclientModule
23: {
24: public function init()
25: {
26: $this->subscribeEvent('Files::PopulateFileItem::after', array($this, 'onAfterPopulateFileItem'));
27: $this->subscribeEvent('Mail::GetBodyStructureParts', array($this, 'onGetBodyStructureParts'));
28: $this->subscribeEvent('Mail::ExtendMessageData', array($this, 'onExtendMessageData'));
29: $this->subscribeEvent('Contacts::CreateContact::after', array($this, 'onAfterCreateOrUpdateContact'));
30: $this->subscribeEvent('Contacts::UpdateContact::after', array($this, 'onAfterCreateOrUpdateContact'));
31: $this->subscribeEvent('Contacts::GetContacts::after', array($this, 'onAfterGetContacts'));
32: $this->subscribeEvent('System::CastExtendedProp', array($this, 'onCastExtendedProp'));
33: }
34:
35: /**
36: * @ignore
37: * @todo not used
38: * @param array $aArgs
39: * @param object $oItem
40: */
41: public function onAfterPopulateFileItem($aArgs, &$oItem)
42: {
43: if ($oItem && '.asc' === \strtolower(\substr(\trim($oItem->Name), -4))) {
44: $oFilesDecorator = \Aurora\System\Api::GetModuleDecorator('Files');
45: if ($oFilesDecorator instanceof \Aurora\System\Module\Decorator) {
46: $mResult = $oFilesDecorator->GetFileContent($aArgs['UserId'], $oItem->TypeStr, $oItem->Path, $oItem->Name);
47: if (isset($mResult)) {
48: $oItem->Content = $mResult;
49: }
50: }
51: }
52: }
53:
54: public function onGetBodyStructureParts($aParts, &$aResultParts)
55: {
56: foreach ($aParts as $oPart) {
57: if ($oPart instanceof \MailSo\Imap\BodyStructure && $oPart->ContentType() === 'text/plain' && '.asc' === \strtolower(\substr(\trim($oPart->FileName()), -4))) {
58: $aResultParts[] = $oPart;
59: }
60: }
61: }
62:
63: public function onExtendMessageData($aData, &$oMessage)
64: {
65: foreach ($aData as $aDataItem) {
66: $oPart = $aDataItem['Part'];
67: $bAsc = $oPart instanceof \MailSo\Imap\BodyStructure && $oPart->ContentType() === 'text/plain' && '.asc' === \strtolower(\substr(\trim($oPart->FileName()), -4));
68: $sData = $aDataItem['Data'];
69: if ($bAsc) {
70: $iMimeIndex = $oPart->PartID();
71: foreach ($oMessage->getAttachments()->GetAsArray() as $oAttachment) {
72: if ($iMimeIndex === $oAttachment->getMimeIndex()) {
73: $oAttachment->setContent($sData);
74: }
75: }
76: }
77: }
78: }
79:
80: public function onAfterCreateOrUpdateContact($aArgs, &$mResult)
81: {
82: if (isset($mResult['UUID']) && isset($aArgs['Contact']['PublicPgpKey'])) {
83: $sPublicPgpKey = $aArgs['Contact']['PublicPgpKey'];
84: if (empty(\trim($sPublicPgpKey))) {
85: $sPublicPgpKey = null;
86: }
87: $oContact = \Aurora\Modules\Contacts\Module::Decorator()->GetContact($mResult['UUID'], $aArgs['UserId']);
88: if ($oContact instanceof Contact) {
89: if (isset($sPublicPgpKey)) {
90: $oContact->setExtendedProp($this->GetName() . '::PgpKey', $sPublicPgpKey);
91: } else {
92: $oContact->unsetExtendedProp($this->GetName() . '::PgpKey');
93: }
94: if (isset($aArgs['Contact']['PgpEncryptMessages']) && is_bool($aArgs['Contact']['PgpEncryptMessages'])) {
95: if ($aArgs['Contact']['Storage'] !== StorageType::Team) {
96: $oContact->setExtendedProp($this->GetName() . '::PgpEncryptMessages', $aArgs['Contact']['PgpEncryptMessages']);
97: } else {
98: $oContact->setExtendedProp($this->GetName() . '::PgpEncryptMessages_' . $aArgs['UserId'], $aArgs['Contact']['PgpEncryptMessages']);
99: }
100: }
101: if (isset($aArgs['Contact']['PgpSignMessages']) && is_bool($aArgs['Contact']['PgpSignMessages'])) {
102: if ($aArgs['Contact']['Storage'] !== StorageType::Team) {
103: $oContact->setExtendedProp($this->GetName() . '::PgpSignMessages', $aArgs['Contact']['PgpSignMessages']);
104: } else {
105: $oContact->setExtendedProp($this->GetName() . '::PgpSignMessages_' . $aArgs['UserId'], $aArgs['Contact']['PgpSignMessages']);
106: }
107: }
108: \Aurora\Modules\Contacts\Module::Decorator()->UpdateContactObject($oContact);
109: if (is_array($mResult) && isset($mResult['ETag'])) {
110: $mResult['ETag'] = $oContact->ETag;
111: }
112: }
113: }
114: }
115:
116: public function onAfterGetContacts($aArgs, &$mResult)
117: {
118: if (isset($mResult['List'])) {
119: $aContactUUIDs = array_map(function ($aValue) {
120: return $aValue['UUID'];
121: }, $mResult['List']);
122: $aContactsInfo = $this->GetContactsWithPublicKeys($aArgs['UserId'], $aContactUUIDs);
123: foreach ($mResult['List'] as &$aContact) {
124: $aContact['HasPgpPublicKey'] = false;
125: $aContact['PgpEncryptMessages'] = false;
126: $aContact['PgpSignMessages'] = false;
127: if (isset($aContactsInfo[$aContact['UUID']])) {
128: $aContact['HasPgpPublicKey'] = true;
129: $aContact['PgpEncryptMessages'] = (bool) $aContactsInfo[$aContact['UUID']]['PgpEncryptMessages'];
130: $aContact['PgpSignMessages'] = (bool) $aContactsInfo[$aContact['UUID']]['PgpSignMessages'];
131: }
132: }
133: }
134: }
135:
136: public function onCastExtendedProp($aArgs, &$mValue)
137: {
138: if ($aArgs['Model'] instanceof Contact &&
139: ($aArgs['PropertyName'] === $this->GetName() . '::PgpEncryptMessages' ||
140: $aArgs['PropertyName'] === $this->GetName() . '::PgpSignMessages')) {
141: $mValue = (bool) $mValue;
142: }
143: }
144:
145: /***** public functions might be called with web API *****/
146: /**
147: * Obtains list of module settings for authenticated user.
148: *
149: * @return array
150: */
151: public function GetSettings()
152: {
153: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
154:
155: $aSettings = [
156: 'EnableModule' => false,
157: 'RememberPassphrase' => false
158: ];
159: $oUser = \Aurora\System\Api::getAuthenticatedUser();
160: if ($oUser && $oUser->isNormalOrTenant()) {
161: if (isset($oUser->{self::GetName().'::EnableModule'})) {
162: $aSettings['EnableModule'] = $oUser->{self::GetName().'::EnableModule'};
163: }
164: if (isset($oUser->{self::GetName().'::RememberPassphrase'})) {
165: $aSettings['RememberPassphrase'] = $oUser->{self::GetName().'::RememberPassphrase'};
166: }
167: }
168: return $aSettings;
169: }
170:
171: public function UpdateSettings($EnableModule, $RememberPassphrase)
172: {
173: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
174:
175: $oUser = \Aurora\System\Api::getAuthenticatedUser();
176: if ($oUser) {
177: if ($oUser->isNormalOrTenant()) {
178: $oCoreDecorator = \Aurora\Modules\Core\Module::Decorator();
179: $oUser->setExtendedProp(self::GetName().'::EnableModule', $EnableModule);
180: if (isset($RememberPassphrase)) {
181: $oUser->setExtendedProp(self::GetName().'::RememberPassphrase', $RememberPassphrase);
182: }
183: return $oCoreDecorator->UpdateUserObject($oUser);
184: }
185: if ($oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
186: return true;
187: }
188: }
189:
190: return false;
191: }
192:
193: public function AddPublicKeyToContactWithUUID($UserId, $UUID, $Key)
194: {
195: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
196:
197: $contact = \Aurora\Modules\Contacts\Module::Decorator()->GetContact($UUID, $UserId);
198: if ($contact instanceof Contact) {
199: $isPersonalContact = $contact->Storage === \Aurora\Modules\Contacts\Enums\StorageType::Personal;
200: $isTeamContact = $contact->Storage === \Aurora\Modules\Contacts\Enums\StorageType::Team;
201: $isItOwnContact = isset($contact->ExtendedInformation['ItsMe']) && $contact->ExtendedInformation['ItsMe'] === true;
202: $isReadOnly = isset($contact->ExtendedInformation['ReadOnly']) && $contact->ExtendedInformation['ReadOnly'] === true;
203: if ($isPersonalContact || $isTeamContact && ($isItOwnContact || !$isReadOnly)) {
204: $contact->setExtendedProp($this->GetName() . '::PgpKey', $Key);
205: return \Aurora\Modules\Contacts\Module::Decorator()->UpdateContactObject($contact);
206: } else {
207: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied);
208: }
209: }
210:
211: return false;
212: }
213:
214: public function AddPublicKeyToContact($UserId, $Email, $Key, $UserName = '')
215: {
216: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
217:
218: $bResult = false;
219: $aUpdatedContactIds = [];
220: if (\MailSo\Base\Validator::SimpleEmailString($Email)) {
221: $aContacts = \Aurora\Modules\Contacts\Module::Decorator()->GetContactsByEmails(
222: $UserId,
223: \Aurora\Modules\Contacts\Enums\StorageType::Personal,
224: [$Email],
225: null,
226: false
227: );
228: if (count($aContacts) === 0) {
229: $mResult = \Aurora\Modules\Contacts\Module::Decorator()->CreateContact(
230: [
231: 'PersonalEmail' => $Email,
232: 'FullName' => $UserName
233: ],
234: $UserId
235: );
236: if (isset($mResult['UUID'])) {
237: $oContact = \Aurora\Modules\Contacts\Module::Decorator()->GetContact($mResult['UUID'], $UserId);
238: if ($oContact instanceof Contact &&
239: $oContact->Storage === \Aurora\Modules\Contacts\Enums\StorageType::Personal) {
240: $aContacts = [$oContact];
241: }
242: }
243: }
244:
245: if ($aContacts && count($aContacts) > 0) {
246: foreach ($aContacts as $oContact) {
247: if ($oContact instanceof Contact &&
248: $oContact->Storage === \Aurora\Modules\Contacts\Enums\StorageType::Personal) {
249: $oContact->setExtendedProp($this->GetName() . '::PgpKey', $Key);
250: \Aurora\Modules\Contacts\Module::Decorator()->UpdateContactObject($oContact);
251: $aUpdatedContactIds[] = $oContact->UUID;
252: }
253: }
254:
255: // $bResult = true;
256: }
257: }
258:
259: return $aUpdatedContactIds;
260: }
261:
262: public function AddPublicKeysToContacts($UserId, $Keys)
263: {
264: $mResult = false;
265: $aUpdatedContactIds = [];
266:
267: foreach ($Keys as $aKey) {
268: if (isset($aKey['Email'], $aKey['Key'])) {
269: $sUserName = isset($aKey['Name']) ? $aKey['Name'] : '';
270: $mResult = $this->AddPublicKeyToContact($UserId, $aKey['Email'], $aKey['Key'], $sUserName);
271: if (is_array($mResult)) {
272: $aUpdatedContactIds = array_merge($aUpdatedContactIds, $mResult);
273: }
274: }
275: }
276:
277: return $aUpdatedContactIds;
278: }
279:
280: public function RemovePublicKeyFromContact($UserId, $Email)
281: {
282: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
283:
284: $bResult = false;
285:
286: if (\MailSo\Base\Validator::SimpleEmailString($Email)) {
287: $aContacts = \Aurora\Modules\Contacts\Module::Decorator()->GetContactsByEmails(
288: $UserId,
289: \Aurora\Modules\Contacts\Enums\StorageType::Personal,
290: [$Email],
291: null,
292: false
293: );
294: if ($aContacts && count($aContacts) > 0) {
295: foreach ($aContacts as $oContact) {
296: if ($oContact instanceof Contact &&
297: $oContact->Storage === \Aurora\Modules\Contacts\Enums\StorageType::Personal) {
298: $oContact->setExtendedProp($this->GetName() . '::PgpKey', null);
299: \Aurora\Modules\Contacts\Module::Decorator()->UpdateContactObject($oContact);
300: }
301: }
302:
303: $bResult = true;
304: }
305: }
306:
307: return $bResult;
308: }
309:
310: public function GetPublicKeysByCountactUUIDs($UserId, $ContactUUIDs)
311: {
312: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
313:
314: $aResult = [];
315:
316: if (count($ContactUUIDs)) {
317: $aContacts = \Aurora\Modules\Contacts\Module::Decorator()->GetContactsByUids($UserId, $ContactUUIDs);
318: if (is_array($aContacts) && count($aContacts) > 0) {
319: foreach ($aContacts as $oContact) {
320: $aResult[] = [
321: 'UUID' => $oContact->UUID,
322: 'Email' => $oContact->ViewEmail,
323: 'PublicPgpKey' => $oContact->{$this->GetName() . '::PgpKey'}
324: ];
325: }
326: }
327: }
328:
329: return $aResult;
330: }
331:
332: protected function getContactPgpData($oContact, $iUserId)
333: {
334: if ($oContact->Storage !== StorageType::Team) {
335: return [
336: 'PgpEncryptMessages' => (bool) $oContact->getExtendedProp($this->GetName() . '::PgpEncryptMessages', false),
337: 'PgpSignMessages' => (bool) $oContact->getExtendedProp($this->GetName() . '::PgpSignMessages', false)
338: ];
339: } else {
340: return [
341: 'PgpEncryptMessages' => (bool) $oContact->getExtendedProp($this->GetName() . '::PgpEncryptMessages_' . $iUserId, false),
342: 'PgpSignMessages' => (bool) $oContact->getExtendedProp($this->GetName() . '::PgpSignMessages_' . $iUserId, false)
343: ];
344: }
345: }
346:
347: public function GetContactsWithPublicKeys($UserId, $UUIDs)
348: {
349: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
350: $mResult = [];
351:
352: $oContacts = Contact::whereIn('UUID', $UUIDs)->whereNotNull('Properties->' . $this->GetName() . '::PgpKey')->get();
353:
354: if (isset($oContacts)) {
355: foreach ($oContacts as $oContact) {
356: $mResult[$oContact->UUID] = $this->getContactPgpData($oContact, $UserId);
357: }
358: }
359:
360: return $mResult;
361: }
362:
363: public function GetPublicKeysFromContacts($UserId)
364: {
365: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
366:
367: $aResult = [];
368:
369: $aContactsInfo = \Aurora\Modules\Contacts\Module::Decorator()->GetContactsInfo(
370: \Aurora\Modules\Contacts\Enums\StorageType::Personal,
371: $UserId,
372: Contact::whereNotNull('Properties->' . $this->GetName() . '::PgpKey')
373: );
374:
375: $aContactUUIDs = [];
376: if (is_array($aContactsInfo['Info']) && count($aContactsInfo['Info']) > 0) {
377: $aContactUUIDs = array_map(function ($aValue) {
378: return $aValue['UUID'];
379: }, $aContactsInfo['Info']);
380: }
381: $aResult = $this->Decorator()->GetPublicKeysByCountactUUIDs($UserId, $aContactUUIDs);
382:
383:
384: return $aResult;
385: }
386:
387: protected function updatePublicKeyFlags($UserId, $oContact, $PgpEncryptMessages = false, $PgpSignMessages = false)
388: {
389: $mResult = false;
390:
391: if ($oContact instanceof Contact) {
392: if ($oContact->Storage === StorageType::Team) {
393: $oContact->setExtendedProp($this->GetName() . '::PgpEncryptMessages_' . $UserId, $PgpEncryptMessages);
394: $oContact->setExtendedProp($this->GetName() . '::PgpSignMessages_' . $UserId, $PgpSignMessages);
395: $mResult = $oContact->save();
396: } else {
397: $oContact->setExtendedProp($this->GetName() . '::PgpEncryptMessages', $PgpEncryptMessages);
398: $oContact->setExtendedProp($this->GetName() . '::PgpSignMessages', $PgpSignMessages);
399: $mResult = \Aurora\Modules\Contacts\Module::Decorator()->UpdateContactObject($oContact);
400: }
401: }
402: return $mResult;
403: }
404:
405: public function UpdateContactPublicKeyFlags($UserId, $UUID, $PgpEncryptMessages = false, $PgpSignMessages = false)
406: {
407: $oContact = \Aurora\Modules\Contacts\Module::Decorator()->GetContact($UUID, $UserId);
408: $mResult = $this->updatePublicKeyFlags($UserId, $oContact, $PgpEncryptMessages, $PgpSignMessages);
409:
410: return $mResult;
411: }
412:
413: protected function getTeamContactByUser($oUser)
414: {
415: $mResult = false;
416:
417: if (Api::GetModuleManager()->IsAllowedModule('TeamContacts')) {
418: $aContacts = \Aurora\Modules\Contacts\Module::Decorator()->GetContactsByEmails(
419: $oUser->Id,
420: \Aurora\Modules\Contacts\Enums\StorageType::Team,
421: [$oUser->PublicId],
422: null,
423: false
424: );
425: if ($aContacts && count($aContacts) > 0) {
426: $oContact = $aContacts[0];
427: if ($oContact instanceof Contact) {
428: $mResult = $oContact;
429: }
430: }
431: }
432:
433: return $mResult;
434: }
435:
436: public function UpdateOwnContactPublicKey($UserId, $PublicPgpKey = '')
437: {
438: $mResult = false;
439:
440: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
441: $oUser = Api::getAuthenticatedUser();
442: if ($oUser) {
443: if ($oUser->Id === $UserId) {
444: $oContact = $this->getTeamContactByUser($oUser);
445: if ($oContact instanceof Contact) {
446: if (!empty($PublicPgpKey)) {
447: $oContact->setExtendedProp($this->GetName() . '::PgpKey', $PublicPgpKey);
448: } else {
449: $oContact->unsetExtendedProp($this->GetName() . '::PgpKey');
450: }
451: $mResult = $oContact->save();
452: }
453: }
454: }
455:
456: return $mResult;
457: }
458:
459: public function GetOwnContactPublicKey($UserId)
460: {
461: $mResult = false;
462:
463: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
464:
465: $oUser = Api::getAuthenticatedUser();
466: if ($oUser) {
467: if ($oUser->Id === $UserId) {
468: $oContact = $this->getTeamContactByUser($oUser);
469: if ($oContact instanceof Contact) {
470: $mResult = $oContact->getExtendedProp($this->GetName() . '::PgpKey', false);
471: }
472: }
473: }
474:
475: return $mResult;
476: }
477:
478:
479:
480: /***** public functions might be called with web API *****/
481: }
482: