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\S3Filestorage;
9:
10: use Afterlogic\DAV\Constants;
11: use Afterlogic\DAV\FS\Shared\File as SharedFile;
12: use Afterlogic\DAV\FS\Shared\Directory as SharedDirectory;
13: use Aurora\Api;
14: use Aws\S3\S3Client;
15: use Aurora\System\Exceptions\ApiException;
16: use Aurora\Modules\PersonalFiles\Module as PersonalFiles;
17:
18: /**
19: * Adds ability to work with S3 file storage inside Aurora Files module.
20: *
21: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
22: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
23: * @copyright Copyright (c) 2023, Afterlogic Corp.
24: *
25: * @property Settings $oModuleSettings
26: *
27: * @package Modules
28: */
29: class Module extends PersonalFiles
30: {
31: protected $aRequireModules = ['PersonalFiles'];
32:
33: protected $oClient = null;
34: protected $sUserPublicId = null;
35:
36: protected $sBucketPrefix;
37: protected $sBucket;
38: protected $sRegion;
39: protected $sHost;
40: protected $sAccessKey;
41: protected $sSecretKey;
42:
43: protected $oTenantForDelete = null;
44: protected $oUserForDelete = null;
45:
46: protected $sTenantName;
47:
48: /***** private functions *****/
49: /**
50: * Initializes Module.
51: *
52: * @ignore
53: */
54: public function init()
55: {
56: $personalFiles = PersonalFiles::getInstance();
57: if ($personalFiles && !$this->oModuleSettings->Disabled) {
58: $personalFiles->setConfig('Disabled', true);
59: }
60:
61: parent::init();
62:
63: $this->subscribeEvent('Core::DeleteTenant::before', array($this, 'onBeforeDeleteTenant'));
64: $this->subscribeEvent('Core::DeleteTenant::after', array($this, 'onAfterDeleteTenant'));
65:
66: $this->subscribeEvent('Core::DeleteUser::before', array($this, 'onBeforeDeleteUser'));
67: $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
68:
69: $this->subscribeEvent('AddToContentSecurityPolicyDefault', array($this, 'onAddToContentSecurityPolicyDefault'));
70:
71: $this->denyMethodsCallByWebApi([
72: 'DeleteUserFolder',
73: 'GetUserByUUID'
74: ]);
75:
76: $sTenantName = $this->getTenantName();
77: $sTenantName = $sTenantName ? $sTenantName : '';
78: $this->sBucketPrefix = $this->oModuleSettings->BucketPrefix;
79: $this->sBucket = \strtolower($this->sBucketPrefix . \str_replace([' ', '.'], '-', $sTenantName));
80: $this->sHost = $this->oModuleSettings->Host;
81: $this->sRegion = $this->oModuleSettings->Region;
82: $this->sAccessKey = $this->oModuleSettings->AccessKey;
83: $this->sSecretKey = $this->oModuleSettings->SecretKey;
84: }
85:
86: /**
87: * @return Module
88: */
89: public static function getInstance()
90: {
91: return parent::getInstance();
92: }
93:
94: /**
95: * @return Module
96: */
97: public static function Decorator()
98: {
99: return parent::Decorator();
100: }
101:
102: /**
103: * @return Settings
104: */
105: public function getModuleSettings()
106: {
107: return $this->oModuleSettings;
108: }
109:
110: public function onAddToContentSecurityPolicyDefault($aArgs, &$aAddDefault)
111: {
112: $aAddDefault[] = "https://" . $this->sHost;
113: $aAddDefault[] = "https://" . $this->sBucket . "." . $this->sHost;
114: }
115:
116: /**
117: * Obtains list of module settings for authenticated user.
118: *
119: * @param int|null $TenantId Tenant ID
120: *
121: * @return array
122: */
123: public function GetSettings($TenantId = null)
124: {
125: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
126:
127: $oSettings = $this->oModuleSettings;
128:
129: // TODO: temporary desabled getting tenant setting. It must return all settings, not only region and host
130: // $aSettings = [];
131: // if (!empty($TenantId)) {
132: // \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
133: // $oTenant = \Aurora\System\Api::getTenantById($TenantId);
134: // $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
135:
136: // if ($oTenant && ($oAuthenticatedUser->isAdmin() || $oAuthenticatedUser->IdTenant === $oTenant->Id)) {
137: // $aSettings = [
138: // 'Region' => $oSettings->GetTenantValue($oTenant->Name, 'Region', ''),
139: // 'Host' => $oSettings->GetTenantValue($oTenant->Name, 'Host', ''),
140: // ];
141: // }
142: // } else {
143: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
144: $aSettings = [
145: 'AccessKey' => $oSettings->AccessKey,
146: 'SecretKey' => $oSettings->SecretKey,
147: 'Region' => $oSettings->Region,
148: 'Host' => $oSettings->Host,
149: 'BucketPrefix' => $oSettings->BucketPrefix,
150: ];
151: // }
152:
153:
154: return $aSettings;
155: }
156:
157: /**
158: * Updates module's settings - saves them to config.json file.
159: *
160: * @param string $AccessKey
161: * @param string $SecretKey
162: * @param string $Region
163: * @param string $Host
164: * @param string $BucketPrefix
165: * @param int|null $TenantId
166: *
167: * @return boolean
168: */
169: public function UpdateS3Settings($AccessKey, $SecretKey, $Region, $Host, $BucketPrefix, $TenantId = null)
170: {
171: $oSettings = $this->oModuleSettings;
172:
173: // TODO: temporary desabled saving tenant setting. It must save all settings, not only region and host
174: // if (!empty($TenantId)) {
175: // \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
176: // $oTenant = \Aurora\System\Api::getTenantById($TenantId);
177: // $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
178:
179: // if ($oTenant && ($oAuthenticatedUser->isAdmin() || $oAuthenticatedUser->IdTenant === $oTenant->Id)) {
180: // return $oSettings->SaveTenantSettings($oTenant->Name, [
181: // 'Region' => $Region,
182: // 'Host' => $Host
183: // ]);
184: // }
185: // } else {
186: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
187:
188: $oSettings->AccessKey = $AccessKey;
189: $oSettings->SecretKey = $SecretKey;
190: $oSettings->Region = $Region;
191: $oSettings->Host = $Host;
192: $oSettings->BucketPrefix = $BucketPrefix;
193: return $oSettings->Save();
194: // }
195:
196: // return false;
197: }
198:
199: public function GetUsersFolders($iTenantId)
200: {
201: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
202:
203: $oUser = \Aurora\System\Api::getAuthenticatedUser();
204: if ($oUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant !== $iTenantId) {
205: throw new ApiException(\Aurora\System\Notifications::AccessDenied, null, 'AccessDenied');
206: } else {
207: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
208: }
209:
210: if (!empty($iTenantId)) {
211: $this->sBucket = $this->getBucketForTenant($iTenantId);
212:
213: $results = $this->getClient(true)->listObjectsV2([
214: 'Bucket' => $this->getBucketForTenant($iTenantId),
215: 'Prefix' => '',
216: 'Delimiter' => '/'
217: ]);
218:
219: $aUsersFolders = [];
220: if (is_array($results['CommonPrefixes']) && count($results['CommonPrefixes']) > 0) {
221: foreach ($results['CommonPrefixes'] as $aPrefix) {
222: if (substr($aPrefix['Prefix'], -1) === '/') {
223: $aUsersFolders[] = \rtrim($aPrefix['Prefix'], '/');
224: }
225: }
226: }
227:
228: return $aUsersFolders;
229: } else {
230: throw new ApiException(\Aurora\System\Notifications::InvalidInputParameter);
231: }
232: }
233:
234:
235: protected function getS3Client()
236: {
237: $options = [
238: 'region' => $this->sRegion,
239: 'version' => 'latest',
240: 'credentials' => [
241: 'key' => $this->sAccessKey,
242: 'secret' => $this->sSecretKey,
243: ]
244: ];
245: if (!empty($this->sHost)) {
246: $options['endpoint'] = 'https://' . $this->sHost;
247: }
248: return new S3Client($options);
249: }
250:
251: /**
252: * Obtains DropBox client if passed $sType is DropBox account type.
253: *
254: * @param boolean $bRenew
255: * @return S3Client
256: */
257: protected function getClient($bRenew = false)
258: {
259: if ($this->oClient === null || $bRenew) {
260: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
261:
262: $this->oClient = $this->getS3Client();
263:
264: if (!$this->oClient->doesBucketExist($this->sBucket)) {
265: $this->oClient->createBucket([
266: 'Bucket' => $this->sBucket
267: ]);
268:
269: $res = $this->oClient->putBucketCors([
270: 'Bucket' => $this->sBucket,
271: 'CORSConfiguration' => [
272: 'CORSRules' => [
273: [
274: 'AllowedHeaders' => [
275: '*',
276: ],
277: 'AllowedMethods' => [
278: 'GET',
279: 'PUT',
280: 'POST',
281: 'DELETE',
282: 'HEAD'
283: ],
284: 'AllowedOrigins' => [
285: (\Aurora\System\Api::isHttps() ? "https" : "http") . "://" . $_SERVER['HTTP_HOST']
286: ],
287: 'MaxAgeSeconds' => 0,
288: ],
289: ],
290: ],
291: 'ContentMD5' => '',
292: ]);
293: }
294: }
295:
296: return $this->oClient;
297: }
298:
299: protected function getUserPublicId()
300: {
301: if (!isset($this->sUserPublicId)) {
302: $oUser = \Aurora\System\Api::getAuthenticatedUser();
303: $this->sUserPublicId = $oUser->PublicId;
304: }
305:
306: return $this->sUserPublicId;
307: }
308:
309: /**
310: * getTenantName
311: *
312: * @return string
313: */
314: protected function getTenantName()
315: {
316: if (!isset($this->sTenantName)) {
317: $this->sTenantName = \Aurora\System\Api::getTenantName();
318: }
319:
320: return $this->sTenantName;
321: }
322:
323: protected function getBucketForTenant($iIdTenant)
324: {
325: $mResult = false;
326: $oTenant = \Aurora\Modules\Core\Module::getInstance()->GetTenantWithoutRoleCheck($iIdTenant);
327: if ($oTenant instanceof \Aurora\Modules\Core\Models\Tenant) {
328: $mResult = \strtolower($this->sBucketPrefix . \str_replace([' ', '.'], '-', $oTenant->Name));
329: }
330:
331: return $mResult;
332: }
333:
334: protected function copyObject($sFromPath, $sToPath, $sOldName, $sNewName, $bIsFolder = false, $bMove = false)
335: {
336: $mResult = false;
337:
338: $sUserPublicId = $this->getUserPublicId();
339:
340: $sSuffix = $bIsFolder ? '/' : '';
341:
342: $sFullFromPath = $this->sBucket . '/' . $sUserPublicId . $sFromPath . '/' . $sOldName . $sSuffix;
343: $sFullToPath = $sUserPublicId . $sToPath . '/' . $sNewName . $sSuffix;
344:
345: $oClient = $this->getClient();
346:
347: $oObject = $oClient->headObject([
348: 'Bucket' => $this->sBucket,
349: 'Key' => urldecode($sUserPublicId . $sFromPath . '/' . $sOldName . $sSuffix)
350: ]);
351:
352: $aMetadata = [];
353: $sMetadataDirective = 'COPY';
354: if ($oObject) {
355: $aMetadata = $oObject->get('Metadata');
356: $aMetadata['filename'] = $sNewName;
357: $sMetadataDirective = 'REPLACE';
358: }
359:
360: $res = $oClient->copyObject([
361: 'Bucket' => $this->sBucket,
362: 'Key' => $sFullToPath,
363: 'CopySource' => $sFullFromPath,
364: 'Metadata' => $aMetadata,
365: 'MetadataDirective' => $sMetadataDirective
366: ]);
367:
368: if ($res) {
369: if ($bMove) {
370: $res = $oClient->deleteObject([
371: 'Bucket' => $this->sBucket,
372: 'Key' => $sUserPublicId . $sFromPath . '/' . $sOldName . $sSuffix
373: ]);
374: }
375: $mResult = true;
376: }
377:
378: return $mResult;
379: }
380:
381: protected function getDirectory($sUserPublicId, $sType, $sPath = '')
382: {
383: $oDirectory = null;
384:
385: if ($sUserPublicId) {
386: $oDirectory = \Afterlogic\DAV\Server::getNodeForPath('files/' . $sType . '/' . \trim($sPath, '/'), $sUserPublicId);
387: }
388:
389: return $oDirectory;
390: }
391:
392: protected function copy($UserId, $FromType, $FromPath, $FromName, $ToType, $ToPath, $ToName, $IsMove = false)
393: {
394: $sUserPublicId = \Aurora\Api::getUserPublicIdById($UserId);
395:
396: $sPath = 'files/' . $FromType . $FromPath . '/' . $FromName;
397: $oItem = \Afterlogic\DAV\Server::getNodeForPath($sPath, $sUserPublicId);
398:
399: $oToDirectory = $this->getDirectory($sUserPublicId, $ToType, $ToPath);
400: $bIsSharedFile = ($oItem instanceof SharedFile || $oItem instanceof SharedDirectory);
401: $bIsSharedToDirectory = ($oToDirectory instanceof SharedDirectory);
402: $iNotPossibleToMoveSharedFileToSharedFolder = 0;
403: if (class_exists('\Aurora\Modules\SharedFiles\Enums\ErrorCodes')) {
404: $iNotPossibleToMoveSharedFileToSharedFolder = \Aurora\Modules\SharedFiles\Enums\ErrorCodes::NotPossibleToMoveSharedFileToSharedFolder;
405: }
406: if ($IsMove && $bIsSharedFile && $bIsSharedToDirectory) {
407: throw new ApiException($iNotPossibleToMoveSharedFileToSharedFolder);
408: }
409:
410: if (($oItem instanceof SharedFile || $oItem instanceof SharedDirectory) && !$oItem->isInherited()) {
411: $oPdo = new \Afterlogic\DAV\FS\Backend\PDO();
412: $oPdo->updateSharedFileSharePath(Constants::PRINCIPALS_PREFIX . $sUserPublicId, $oItem->getName(), $ToName, $FromPath, $ToPath, $oItem->getGroupId());
413:
414: $oItem = $oItem->getNode();
415: } else {
416: $ToName = $this->getManager()->getNonExistentFileName(
417: $sUserPublicId,
418: $ToType,
419: $ToPath,
420: $ToName
421: );
422: if (!$bIsSharedToDirectory) {
423: $oItem->copyObjectTo($ToType, $ToPath, $ToName);
424: } else {
425: $oToDirectory->createFile($ToName, $oItem->get(false));
426: }
427: $oPdo = new \Afterlogic\DAV\FS\Backend\PDO();
428: $oPdo->updateShare(Constants::PRINCIPALS_PREFIX . $sUserPublicId, $FromType, $FromPath . '/' . $FromName, $ToType, $ToPath . '/' . $ToName);
429: if ($IsMove) {
430: \Afterlogic\DAV\Server::deleteNode($sPath, $sUserPublicId);
431: }
432: }
433: }
434:
435: // /**
436: // * Moves file if $aData['Type'] is DropBox account type.
437: // *
438: // * @ignore
439: // * @param array $aData
440: // */
441: // public function onAfterMove(&$aArgs, &$mResult)
442: // {
443: // \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
444:
445: // if ($this->checkStorageType($aArgs['FromType']))
446: // {
447: // $mResult = false;
448:
449: // $UserId = $aArgs['UserId'];
450: // Api::CheckAccess($UserId);
451:
452: // // if ($aArgs['ToType'] === $aArgs['FromType'])
453: // // {
454: // foreach ($aArgs['Files'] as $aFile)
455: // {
456: // $ToName = isset($aFile['NewName']) ? $aFile['NewName'] : $aFile['Name'];
457: // $this->copy($UserId, $aArgs['FromType'], $aFile['FromPath'], $aFile['Name'], $aArgs['ToType'], $aArgs['ToPath'], $ToName, true);
458: // }
459: // $mResult = true;
460: // // }
461: // }
462:
463: // }
464:
465: // /**
466: // * Copies file if $aData['Type'] is DropBox account type.
467: // *
468: // * @ignore
469: // * @param array $aData
470: // */
471: // public function onAfterCopy(&$aArgs, &$mResult)
472: // {
473: // \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
474:
475: // if ($this->checkStorageType($aArgs['FromType']))
476: // {
477: // $mResult = false;
478:
479: // $UserId = $aArgs['UserId'];
480: // Api::CheckAccess($UserId);
481:
482: // // if ($aArgs['ToType'] === $aArgs['FromType'])
483: // // {
484: // foreach ($aArgs['Files'] as $aFile)
485: // {
486: // $ToName = isset($aFile['NewName']) ? $aFile['NewName'] : $aFile['Name'];
487: // $this->copy($UserId, $aArgs['FromType'], $aFile['FromPath'], $aFile['Name'], $aArgs['ToType'], $aArgs['ToPath'], $ToName, false);
488: // }
489: // $mResult = true;
490: // // }
491: // }
492: // }
493:
494: /**
495: * @ignore
496: * @param array $aArgs Arguments of event.
497: * @param mixed $mResult Is passed by reference.
498: */
499: public function onAfterGetQuota($aArgs, &$mResult)
500: {
501: if ($this->checkStorageType($aArgs['Type'])) {
502: $aQuota = [0, 0];
503: $oNode = \Afterlogic\DAV\Server::getNodeForPath('files/' . static::$sStorageType);
504:
505: if (is_a($oNode, 'Afterlogic\\DAV\\FS\\S3\\' . ucfirst(static::$sStorageType) . '\\Root')) {
506: $aQuota = $oNode->getQuotaInfo();
507: }
508: $iSpaceLimitMb = $aQuota[1];
509:
510: $aArgs = [
511: 'UserId' => \Aurora\System\Api::getAuthenticatedUserId()
512: ];
513: $this->broadcastEvent(
514: 'GetUserSpaceLimitMb',
515: $aArgs,
516: $iSpaceLimitMb
517: );
518:
519: $mResult = [
520: 'Used' => $aQuota[0],
521: 'Limit' => $iSpaceLimitMb
522: ];
523: }
524: }
525:
526: /**
527: * @ignore
528: * @param array $aArgs Arguments of event.
529: * @param mixed $mResult Is passed by reference.
530: */
531: public function onAfterGetSubModules($aArgs, &$mResult)
532: {
533: array_unshift($mResult, 's3.' . static::$sStorageType);
534: }
535:
536:
537: protected function isNeedToReturnBody()
538: {
539: $sMethod = $this->oHttp->GetPost('Method', null);
540:
541: return ((string) \Aurora\System\Router::getItemByIndex(2, '') === 'thumb' ||
542: $sMethod === 'SaveFilesAsTempFiles' ||
543: $sMethod === 'GetFilesForUpload'
544: );
545: }
546:
547: protected function isNeedToReturnWithContectDisposition()
548: {
549: $sAction = (string) \Aurora\System\Router::getItemByIndex(2, 'download');
550: return $sAction === 'download';
551: }
552:
553: /**
554: * Puts file content to $mResult.
555: * @ignore
556: * @param array $aArgs Arguments of event.
557: * @param mixed $mResult Is passed by reference.
558: */
559: public function onGetFile($aArgs, &$mResult)
560: {
561: if ($this->checkStorageType($aArgs['Type'])) {
562: $UserId = $aArgs['UserId'];
563: Api::CheckAccess($UserId);
564:
565: $sUserPiblicId = \Aurora\Api::getUserPublicIdById($UserId);
566:
567: try {
568: $sPath = 'files/' . $aArgs['Type'] . $aArgs['Path'] . '/' . $aArgs['Name'];
569: /** @var \Afterlogic\DAV\FS\S3\File $oNode */
570: $oNode = \Afterlogic\DAV\Server::getNodeForPath($sPath, $sUserPiblicId);
571:
572: $sExt = \pathinfo($aArgs['Name'], PATHINFO_EXTENSION);
573:
574: $bNoRedirect = (isset($aArgs['NoRedirect']) && $aArgs['NoRedirect']) ? true : false;
575:
576: if ($oNode instanceof \Afterlogic\DAV\FS\File) {
577: if ($this->isNeedToReturnBody() || \strtolower($sExt) === 'url' || $bNoRedirect) {
578: $mResult = $oNode->get(false);
579: } elseif ($this->isNeedToReturnWithContectDisposition()) {
580: $oNode->getWithContentDisposition();
581: } else {
582: $oNode->get(true);
583: }
584: }
585: } catch (\Sabre\DAV\Exception\NotFound $oEx) {
586: $mResult = false;
587: // echo(\Aurora\System\Managers\Response::GetJsonFromObject('Json', \Aurora\System\Managers\Response::FalseResponse(__METHOD__, 404, 'Not Found')));
588: $this->oHttp->StatusHeader(404);
589: exit;
590: }
591:
592: return true;
593: }
594: }
595:
596: public function onBeforeDeleteTenant($aArgs, &$mResult)
597: {
598: $this->oTenantForDelete = \Aurora\Modules\Core\Module::Decorator()->GetTenantWithoutRoleCheck($aArgs['TenantId']);
599: }
600:
601: public function onAfterDeleteTenant($aArgs, &$mResult)
602: {
603: if ($this->oTenantForDelete instanceof \Aurora\Modules\Core\Models\Tenant) {
604: try {
605: $oS3Client = $this->getS3Client();
606: $oS3Client->deleteBucket([
607: 'Bucket' => \strtolower($this->sBucketPrefix . \str_replace([' ', '.'], '-', $this->oTenantForDelete->Name))
608: ]);
609: $this->oTenantForDelete = null;
610: } catch(\Exception $oEx) {
611: }
612: }
613: }
614:
615: public function onBeforeDeleteUser($aArgs, &$mResult)
616: {
617: if (isset($aArgs['UserId'])) {
618: $this->oUserForDelete = \Aurora\System\Api::getUserById($aArgs['UserId']);
619: }
620: }
621:
622: public function onAfterDeleteUser($aArgs, $mResult)
623: {
624: if ($this->oUserForDelete instanceof \Aurora\Modules\Core\Models\User) {
625: if ($this->DeleteUserFolder($this->oUserForDelete->IdTenant, $this->oUserForDelete->PublicId)) {
626: $this->oUserForDelete = null;
627: }
628: }
629: }
630:
631: public function DeleteUserFolder($IdTenant, $PublicId)
632: {
633: $bResult = false;
634: try {
635: $oS3Client = $this->getS3Client();
636: $res = $oS3Client->deleteMatchingObjects(
637: $this->getBucketForTenant($IdTenant),
638: $PublicId . '/'
639: );
640: $bResult = true;
641: } catch(\Exception $oEx) {
642: $bResult = false;
643: }
644:
645: return $bResult;
646: }
647:
648: public function TestConnection($Region, $Host, $AccessKey = null, $SecretKey = null, $TenantId = null)
649: {
650: $mResult = true;
651:
652: if (isset($TenantId)) {
653: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
654:
655: $oTenant = \Aurora\System\Api::getTenantById($TenantId);
656:
657: if ($oTenant) {
658: $AccessKey = $this->oModuleSettings->AccessKey;
659: $SecretKey = $this->oModuleSettings->SecretKey;
660: }
661: } else {
662: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
663: }
664: try {
665: $options = [
666: 'region' => $Region,
667: 'version' => 'latest',
668: 'credentials' => [
669: 'key' => $AccessKey,
670: 'secret' => $SecretKey,
671: ]
672: ];
673: if (!empty($Host)) {
674: $options['endpoint'] = 'https://' . $Host;
675: }
676: $s3Client = new S3Client($options);
677:
678: $buckets = $s3Client->listBuckets();
679: } catch(\Exception $e) {
680: $mResult = false;
681: Api::LogException($e);
682: }
683: return $mResult;
684: }
685: }
686: