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\StandardAuth;
9:
10: use Aurora\Modules\StandardAuth\Models\Account;
11:
12: /**
13: * This module provides API for authentication by login/password that relies on database.
14: *
15: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
16: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
17: * @copyright Copyright (c) 2023, Afterlogic Corp.
18: *
19: * @property Settings $oModuleSettings
20: *
21: * @package Modules
22: */
23: class Module extends \Aurora\System\Module\AbstractModule
24: {
25: public $oApiAccountsManager = null;
26:
27: /**
28: * @return Module
29: */
30: public static function getInstance()
31: {
32: return parent::getInstance();
33: }
34:
35: /**
36: * @return Module
37: */
38: public static function Decorator()
39: {
40: return parent::Decorator();
41: }
42:
43: /**
44: * @return Settings
45: */
46: public function getModuleSettings()
47: {
48: return $this->oModuleSettings;
49: }
50:
51: public function getAccountsManager()
52: {
53: if ($this->oApiAccountsManager === null) {
54: $this->oApiAccountsManager = new Managers\Accounts\Manager($this);
55: }
56:
57: return $this->oApiAccountsManager;
58: }
59:
60: /***** private functions *****/
61: /**
62: * Initializes module.
63: *
64: * @ignore
65: */
66: public function init()
67: {
68: $this->subscribeEvent('Login', array($this, 'onLogin'), 90);
69: $this->subscribeEvent('Register', array($this, 'onRegister'));
70: $this->subscribeEvent('CheckAccountExists', array($this, 'onCheckAccountExists'));
71: $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
72: $this->subscribeEvent('Core::GetAccounts', array($this, 'onGetAccounts'));
73: $this->subscribeEvent('Core::GetAccountUsedToAuthorize', array($this, 'onGetAccountUsedToAuthorize'), 200);
74: $this->subscribeEvent('StandardResetPassword::ChangeAccountPassword', array($this, 'onChangeAccountPassword'));
75:
76: $this->denyMethodCallByWebApi('CreateAccount');
77: $this->denyMethodCallByWebApi('SaveAccount');
78: }
79:
80: /**
81: * Tries to log in with specified credentials via StandardAuth module. Writes to $mResult array with auth token data if logging in was successfull.
82: * @ignore
83: * @param array $aArgs Credentials for logging in.
84: * @param mixed $mResult Is passed by reference.
85: */
86: public function onLogin($aArgs, &$mResult)
87: {
88: $oAccount = $this->getAccountsManager()->getAccountByCredentials(
89: $aArgs['Login'],
90: $aArgs['Password']
91: );
92:
93: if ($oAccount) {
94: $mResult = \Aurora\System\UserSession::getTokenData($oAccount, $aArgs['SignMe']);
95: return true;
96: }
97: }
98:
99: /**
100: * Creates account with specified credentials.
101: * @ignore
102: * @param array $aArgs New account credentials.
103: * @param Models\Account|bool $mResult Is passed by reference.
104: */
105: public function onRegister($aArgs, &$mResult)
106: {
107: $mResult = $this->CreateAccount(
108: 0,
109: $aArgs['UserId'],
110: $aArgs['Login'],
111: $aArgs['Password']
112: );
113: }
114:
115: /**
116: * Checks if module has account with specified login.
117: * @ignore
118: * @param array $aArgs
119: * @throws \Aurora\System\Exceptions\ApiException
120: */
121: public function onCheckAccountExists($aArgs)
122: {
123: $oAccount = new Models\Account();
124: $oAccount->Login = $aArgs['Login'];
125: if ($this->getAccountsManager()->isExists($oAccount)) {
126: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccountExists);
127: }
128: }
129:
130: /**
131: * Deletes all basic accounts which are owned by the specified user.
132: * @ignore
133: * @param array $aArgs
134: * @param mixed $mResult.
135: */
136: public function onAfterDeleteUser($aArgs, $mResult)
137: {
138: if ($mResult) {
139: Account::where('IdUser', $aArgs['UserId'])->delete();
140: }
141: }
142:
143: /**
144: *
145: * @param array $aArgs
146: * @param array $aResult
147: */
148: public function onGetAccounts($aArgs, &$aResult)
149: {
150: if (isset($aArgs['UserId'])) {
151: $mResult = $this->getAccountsManager()->getUserAccounts($aArgs['UserId']);
152: foreach ($mResult as $oItem) {
153: $aResult[] = [
154: 'Type' => $oItem->getName(),
155: 'Module' => $this->GetName(),
156: 'Id' => $oItem->Id,
157: 'Login' => $oItem->Login
158: ];
159: }
160: }
161: }
162:
163: public function onGetAccountUsedToAuthorize($aArgs, &$mResult)
164: {
165: $oAccount = $this->getAccountsManager()->getAccountUsedToAuthorize($aArgs['Login']);
166: if ($oAccount) {
167: $mResult = $oAccount;
168: return true;
169: }
170: }
171:
172: /**
173: * Changes password for account if allowed.
174: * @param array $aArguments
175: * @param mixed $mResult
176: */
177: public function onChangeAccountPassword($aArguments, &$mResult)
178: {
179: $bPasswordChanged = false;
180: $bBreakSubscriptions = false;
181: $oAccount = $aArguments['Account'];
182:
183: if ($oAccount instanceof Account && $oAccount->getPassword() === $aArguments['CurrentPassword']) {
184: $bPasswordChanged = $this->changePassword($oAccount, $aArguments['NewPassword']);
185: $bBreakSubscriptions = true;
186: }
187:
188: if (is_array($mResult)) {
189: $mResult['AccountPasswordChanged'] = $mResult['AccountPasswordChanged'] || $bPasswordChanged;
190: }
191:
192: return $bBreakSubscriptions;
193: }
194:
195: protected function changePassword($oAccount, $sNewPassword)
196: {
197: $bResult = false;
198:
199: if ($oAccount instanceof Account && $sNewPassword) {
200: $oAccount->setPassword($sNewPassword);
201: $bResult = $this->getAccountsManager()->updateAccount($oAccount);
202: } else {
203: \Aurora\System\Api::LogEvent('password-change-failed: ' . $oAccount->Login, self::GetName());
204: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountNewPasswordRejected);
205: }
206:
207: return $bResult;
208: }
209:
210: /***** private functions *****/
211:
212: /***** public functions *****/
213: /**
214: * Creates account with credentials.
215: * Denied for web API call
216: *
217: * @param int $iTenantId Tenant identifier.
218: * @param int $iUserId User identifier.
219: * @param string $sLogin New account login.
220: * @param string $sPassword New account password.
221: * @return Models\Account|bool
222: * @throws \Aurora\System\Exceptions\ApiException
223: */
224: public function CreateAccount($iTenantId = 0, $iUserId = 0, $sLogin = '', $sPassword = '')
225: {
226: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
227:
228: $aArgs = array(
229: 'Login' => $sLogin
230: );
231: $this->broadcastEvent(
232: 'CheckAccountExists',
233: $aArgs
234: );
235:
236: if ($iUserId > 0) {
237: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($iUserId);
238: } else {
239: $sPublicId = (string)$sLogin;
240: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
241: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($sPublicId);
242:
243: if (!$oUser) {
244: $iUserId = \Aurora\Modules\Core\Module::Decorator()->CreateUser($iTenantId, $sPublicId);
245: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($iUserId);
246: }
247: \Aurora\System\Api::skipCheckUserRole($bPrevState);
248: }
249:
250: // $mResult = null;
251: // $aArgs = array(
252: // 'TenantId' => $iTenantId,
253: // 'UserId' => $iUserId,
254: // 'login' => $sLogin,
255: // 'password' => $sPassword
256: // );
257: // $this->broadcastEvent(
258: // 'CreateAccount',
259: // $aArgs,
260: // $mResult
261: // );
262:
263: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
264: $oAccount = new Models\Account();
265:
266: $oAccount->IdUser = $oUser->Id;
267: $oAccount->Login = $sLogin;
268: $oAccount->setPassword($sPassword);
269:
270: if ($this->getAccountsManager()->isExists($oAccount)) {
271: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccountExists);
272: }
273:
274: $this->getAccountsManager()->createAccount($oAccount);
275: return $oAccount ? array(
276: 'EntityId' => $oAccount->Id
277: ) : false;
278: } else {
279: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::CanNotCreateAccount);
280: }
281:
282: return false;
283: }
284: /**
285: * Updates account.
286: *
287: * @param \Aurora\Modules\StandardAuth\Models\Account $oAccount
288: * @return bool
289: * @throws \Aurora\System\Exceptions\ApiException
290: */
291: public function SaveAccount($oAccount)
292: {
293: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
294:
295: if ($oAccount instanceof Models\Account) {
296: $this->getAccountsManager()->createAccount($oAccount);
297:
298: return $oAccount ? array(
299: 'EntityId' => $oAccount->Id
300: ) : false;
301: } else {
302: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
303: }
304:
305: return false;
306: }
307: /***** public functions *****/
308:
309: /***** public functions might be called with web API *****/
310: /**
311: * @apiDefine StandardAuth Standard Auth Module
312: * This module provides API for authentication by login/password that relies on database.
313: */
314:
315: /**
316: * @api {post} ?/Api/ CreateAuthenticatedUserAccount
317: * @apiName CreateAuthenticatedUserAccount
318: * @apiGroup StandardAuth
319: * @apiDescription Creates basic account for specified user.
320: *
321: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
322: * @apiHeaderExample {json} Header-Example:
323: * {
324: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
325: * }
326: *
327: * @apiParam {string=StandardAuth} Module Module name.
328: * @apiParam {string=CreateAuthenticatedUserAccount} Method Method name.
329: * @apiParam {string} Parameters JSON.stringified object <br>
330: * {<br>
331: * &emsp; **Login** *string* New account login.<br>
332: * &emsp; **Password** *string* Password New account password.<br>
333: * }
334: *
335: * @apiParamExample {json} Request-Example:
336: * {
337: * Module: 'StandardAuth',
338: * Method: 'CreateAuthenticatedUserAccount',
339: * Parameters: '{ Login: "login_value", Password: "password_value" }'
340: * }
341: *
342: * @apiSuccess {object[]} Result Array of response objects.
343: * @apiSuccess {string} Result.Module Module name.
344: * @apiSuccess {string} Result.Method Method name.
345: * @apiSuccess {bool} Result.Result Indicates if account was created successfully.
346: * @apiSuccess {int} [Result.ErrorCode] Error code.
347: *
348: * @apiSuccessExample {json} Success response example:
349: * {
350: * Module: 'StandardAuth',
351: * Method: 'CreateAuthenticatedUserAccount',
352: * Result: true
353: * }
354: *
355: * @apiSuccessExample {json} Error response example:
356: * {
357: * Module: 'StandardAuth',
358: * Method: 'CreateAuthenticatedUserAccount',
359: * Result: false,
360: * ErrorCode: 102
361: * }
362: */
363: /**
364: * Creates basic account for specified user.
365: *
366: * @param string $Login New account login.
367: * @param string $Password New account password.
368: * @return bool
369: */
370: public function CreateAuthenticatedUserAccount($TenantId, $Login, $Password)
371: {
372: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
373:
374: $UserId = \Aurora\System\Api::getAuthenticatedUserId();
375: $result = false;
376:
377: if ($UserId) {
378: $result = $this->CreateAccount($TenantId, $UserId, $Login, $Password);
379: }
380:
381: return $result;
382: }
383:
384: /**
385: * @api {post} ?/Api/ UpdateAccount
386: * @apiName UpdateAccount
387: * @apiGroup StandardAuth
388: * @apiDescription Updates existing basic account.
389: *
390: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
391: * @apiHeaderExample {json} Header-Example:
392: * {
393: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
394: * }
395: *
396: * @apiParam {string=StandardAuth} Module Module name.
397: * @apiParam {string=UpdateAccount} Method Method name.
398: * @apiParam {string} Parameters JSON.stringified object <br>
399: * {<br>
400: * &emsp; **AccountId** *int* AccountId Identifier of account to update.<br>
401: * &emsp; **Login** *string* New value of account login. *optional*<br>
402: * &emsp; **Password** *string* New value of account password. *optional*<br>
403: * }
404: *
405: * @apiParamExample {json} Request-Example:
406: * {
407: * Module: 'StandardAuth',
408: * Method: 'UpdateAccount',
409: * Parameters: '{ AccountId: 123, Login: "login_value", Password: "password_value" }'
410: * }
411: *
412: * @apiSuccess {object[]} Result Array of response objects.
413: * @apiSuccess {string} Result.Module Module name.
414: * @apiSuccess {string} Result.Method Method name.
415: * @apiSuccess {mixed} Result.Result Object in case of success, otherwise **false**.
416: * @apiSuccess {string} Result.Result.EntityId Identifier of updated account.
417: * @apiSuccess {int} [Result.ErrorCode] Error code.
418: *
419: * @apiSuccessExample {json} Success response example:
420: * {
421: * Module: 'StandardAuth',
422: * Method: 'UpdateAccount',
423: * Result: true
424: * }
425: *
426: * @apiSuccessExample {json} Error response example:
427: * {
428: * Module: 'StandardAuth',
429: * Method: 'UpdateAccount',
430: * Result: false,
431: * ErrorCode: 102
432: * }
433: */
434: /**
435: * Updates existing basic account.
436: *
437: * @param int $AccountId Identifier of account to update.
438: * @param string $CurrentPassword Current value of account password.
439: * @param string $Password New value of account password.
440: * @return array|bool
441: * @throws \Aurora\System\Exceptions\ApiException
442: */
443: public function UpdateAccount($AccountId = 0, $CurrentPassword = '', $Password = '')
444: {
445: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
446:
447: $oUser = \Aurora\System\Api::getAuthenticatedUser();
448:
449: if ($AccountId > 0) {
450: $oAccount = $this->getAccountsManager()->getAccountById($AccountId);
451:
452: if (!empty($oAccount)) {
453: if ($oAccount->IdUser !== $oUser->Id) {
454: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
455: }
456:
457: if ($oUser->Role !== \Aurora\System\Enums\UserRole::SuperAdmin && $oAccount->getPassword() !== $CurrentPassword) {
458: \Aurora\System\Api::LogEvent('password-change-failed: ' . $oAccount->Login, self::GetName());
459: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountOldPasswordNotCorrect);
460: }
461:
462: $this->changePassword($oAccount, $Password);
463: }
464:
465: return $oAccount ? array('EntityId' => $oAccount->Id) : false;
466: } else {
467: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
468: }
469:
470: return false;
471: }
472:
473: /**
474: * @api {post} ?/Api/ DeleteAccount
475: * @apiName DeleteAccount
476: * @apiGroup StandardAuth
477: * @apiDescription Deletes basic account.
478: *
479: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
480: * @apiHeaderExample {json} Header-Example:
481: * {
482: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
483: * }
484: *
485: * @apiParam {string=StandardAuth} Module Module name.
486: * @apiParam {string=DeleteAccount} Method Method name.
487: * @apiParam {string} Parameters JSON.stringified object <br>
488: * {<br>
489: * &emsp; **AccountId** *int* Identifier of account to delete.<br>
490: * }
491: *
492: * @apiParamExample {json} Request-Example:
493: * {
494: * Module: 'StandardAuth',
495: * Method: 'DeleteAccount',
496: * Parameters: '{ AccountId: 123 }'
497: * }
498: *
499: * @apiSuccess {object[]} Result Array of response objects.
500: * @apiSuccess {string} Result.Module Module name.
501: * @apiSuccess {string} Result.Method Method name.
502: * @apiSuccess {bool} Result.Result Indicates if account was deleted successfully.
503: * @apiSuccess {int} [Result.ErrorCode] Error code.
504: *
505: * @apiSuccessExample {json} Success response example:
506: * {
507: * Module: 'StandardAuth',
508: * Method: 'DeleteAccount',
509: * Result: true
510: * }
511: *
512: * @apiSuccessExample {json} Error response example:
513: * {
514: * Module: 'StandardAuth',
515: * Method: 'DeleteAccount',
516: * Result: false,
517: * ErrorCode: 102
518: * }
519: */
520: /**
521: * Deletes basic account.
522: *
523: * @param int $AccountId Identifier of account to delete.
524: * @return bool
525: * @throws \Aurora\System\Exceptions\ApiException
526: */
527: public function DeleteAccount($AccountId = 0)
528: {
529: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
530:
531: $oUser = \Aurora\System\Api::getAuthenticatedUser();
532:
533: $bResult = false;
534:
535: if ($AccountId > 0) {
536: $oAccount = $this->getAccountsManager()->getAccountById($AccountId);
537:
538: if (!empty($oAccount) && ($oAccount->IdUser === $oUser->Id ||
539: $oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin ||
540: $oUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
541: $bResult = $this->getAccountsManager()->deleteAccount($oAccount);
542: }
543:
544: return $bResult;
545: } else {
546: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
547: }
548: }
549:
550: /**
551: * @api {post} ?/Api/ GetUserAccounts
552: * @apiName GetUserAccounts
553: * @apiGroup StandardAuth
554: * @apiDescription Obtains basic account for specified user.
555: *
556: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
557: * @apiHeaderExample {json} Header-Example:
558: * {
559: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
560: * }
561: *
562: * @apiParam {string=StandardAuth} Module Module name.
563: * @apiParam {string=GetUserAccounts} Method Method name.
564: * @apiParam {string} Parameters JSON.stringified object <br>
565: * {<br>
566: * &emsp; **UserId** *int* User identifier.<br>
567: * }
568: *
569: * @apiParamExample {json} Request-Example:
570: * {
571: * Module: 'StandardAuth',
572: * Method: 'GetUserAccounts',
573: * Parameters: '{ UserId: 123 }'
574: * }
575: *
576: * @apiSuccess {object[]} Result Array of response objects.
577: * @apiSuccess {string} Result.Module Module name.
578: * @apiSuccess {string} Result.Method Method name.
579: * @apiSuccess {mixed} Result.Result List of account objects in case of success, otherwise **false**. Account object is like {id: 234, login: 'account_login'}.
580: * @apiSuccess {int} [Result.ErrorCode] Error code.
581: *
582: * @apiSuccessExample {json} Success response example:
583: * {
584: * Module: 'StandardAuth',
585: * Method: 'GetUserAccounts',
586: * Result: [{id: 234, login: 'account_login234'}, {id: 235, login: 'account_login235'}]
587: * }
588: *
589: * @apiSuccessExample {json} Error response example:
590: * {
591: * Module: 'StandardAuth',
592: * Method: 'GetUserAccounts',
593: * Result: false,
594: * ErrorCode: 102
595: * }
596: */
597: /**
598: * Obtains basic account for specified user.
599: *
600: * @param int $UserId User identifier.
601: * @return array|bool
602: */
603: public function GetUserAccounts($UserId)
604: {
605: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
606:
607: $oUser = \Aurora\System\Api::getAuthenticatedUser();
608: if ($oUser->isNormalOrTenant() && $oUser->Id != $UserId) {
609: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied);
610: }
611:
612: $aAccounts = array();
613: $mResult = $this->getAccountsManager()->getUserAccounts($UserId);
614:
615: foreach ($mResult as $aItem) {
616: $aAccounts[] = array(
617: 'id' => $aItem['Id'],
618: 'login' => $aItem['Login']
619: );
620: }
621:
622: return $aAccounts;
623: }
624: /***** public functions might be called with web API *****/
625: }
626: