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