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\System;
9:
10: use Aurora\System\Models\AuthToken;
11:
12: /**
13: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
14: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
15: * @copyright Copyright (c) 2019, Afterlogic Corp.
16: *
17: * @package Api
18: */
19: class UserSession
20: {
21: public const TOKEN_VERSION = '3.1';
22:
23: public static $aTokensCache = [];
24:
25: public static function getTokenData($oAccount, $bSignMe = true)
26: {
27: return [
28: 'token' => 'auth',
29: 'sign-me' => $bSignMe,
30: 'id' => $oAccount->IdUser,
31: 'account' => $oAccount->Id,
32: 'account_type' => get_class($oAccount)
33: ];
34: }
35:
36:
37: public function Set($aData, $iTime = 0, $iExpire = 0)
38: {
39: $aData['@time'] = $iTime;
40: $aData['@expire'] = $iExpire;
41: $aData['@ver'] = self::TOKEN_VERSION;
42: if ($iExpire > 0) {
43: $aData['@expire'] = $iExpire;
44: }
45: $sAuthToken = Api::EncodeKeyValues(
46: $aData
47: );
48:
49: if (\Aurora\Api::GetSettings()->GetValue('StoreAuthTokenInDB', false)) {
50:
51: $account = $aData['account'] ?? 0;
52: $account_type = $aData['account_type'] ?? '';
53:
54: $this->SetToDB($aData['id'], $account, $account_type, $sAuthToken);
55: }
56:
57: return $sAuthToken;
58: }
59:
60: public function UpdateTimestamp($sAuthToken, $iTime = 0)
61: {
62: $aData = $this->Get($sAuthToken);
63: return $this->Set($aData, $iTime);
64: }
65:
66: public function Get($sAuthToken)
67: {
68: $mAuthTokenData = false;
69: $mResult = true;
70:
71: if (is_string($sAuthToken) && strlen($sAuthToken) !== 0) {
72: $bStoreAuthTokenInDB = \Aurora\Api::GetSettings()->GetValue('StoreAuthTokenInDB', false);
73:
74: // check if the auth token is stored in the database
75: if ($bStoreAuthTokenInDB && !$this->GetFromDB($sAuthToken)) {
76: return false;
77: }
78:
79: $mAuthTokenData = Api::DecodeKeyValues($sAuthToken);
80:
81: // checking the validity of auth token data
82: if ($mAuthTokenData && isset($mAuthTokenData['id'])) {
83: $oUser = Api::getUserById((int) $mAuthTokenData['id']);
84:
85: // check if user is disabled
86: if ($oUser && $oUser->IsDisabled) {
87: $mResult = false;
88: }
89:
90: // check auth token version
91: if ($mResult) {
92: if ((isset($mAuthTokenData['@ver']) && $mAuthTokenData['@ver'] !== self::TOKEN_VERSION) || !isset($mAuthTokenData['@ver'])) {
93: $mResult = false;
94: }
95: }
96:
97: // check auth token expiration date
98: if ($mResult) {
99: $iExpireTime = (int) isset($mAuthTokenData['@expire']) ? $mAuthTokenData['@expire'] : 0;
100: if ($iExpireTime > 0 && $iExpireTime < time()) {
101: $mResult = false;
102: }
103: }
104:
105: // checking the token is valid from timestamp
106: if ($mResult && isset($mAuthTokenData['account'], $mAuthTokenData['account_type']) && class_exists($mAuthTokenData['account_type'])) {
107: $iTime = (int) $mAuthTokenData['@time']; // 0 means that signMe was true when user logged in, so there is no need to check it in that case
108: $oAccount = $mAuthTokenData['account_type']::where('Id', $mAuthTokenData['account'])->first();
109: if ($oAccount && $iTime !== 0 && (int) $oAccount->getExtendedProp('TokensValidFromTimestamp') > $iTime) {
110: $mResult = false;
111: }
112: }
113:
114: // check user sessions that are considered expired
115: if ($mResult) {
116: if ((isset($mAuthTokenData['sign-me']) && !((bool) $mAuthTokenData['sign-me'])) || (!isset($mAuthTokenData['sign-me']))) {
117: $iTime = 0;
118: if (isset($mAuthTokenData['@time'])) {
119: $iTime = (int) $mAuthTokenData['@time'];
120: }
121: $iExpireUserSessionsBeforeTimestamp = \Aurora\System\Api::GetSettings()->GetValue("ExpireUserSessionsBeforeTimestamp", 0);
122: if ($iExpireUserSessionsBeforeTimestamp > $iTime && $iTime > 0) {
123: $mResult = false;
124: }
125: }
126: }
127: } else {
128: $mResult = false;
129: }
130:
131: if (!$mResult) {
132: $this->Delete($sAuthToken);
133: $mAuthTokenData = $mResult;
134:
135: \Aurora\System\Api::Log('User session expired: ');
136: \Aurora\System\Api::LogObject($mAuthTokenData);
137: }
138: }
139:
140: return $mAuthTokenData;
141: }
142:
143: public function Delete($sAuthToken)
144: {
145: if (is_string($sAuthToken) && \Aurora\Api::GetSettings()->GetValue('StoreAuthTokenInDB', false)) {
146: try {
147: $this->DeleteFromDB($sAuthToken);
148: } catch (\Aurora\System\Exceptions\DbException $oEx) {
149: // DB is not configured
150: }
151: }
152: }
153:
154: public function DeleteAllUserSessions($iUserId)
155: {
156: return AuthToken::where('UserId', $iUserId)->delete();
157: }
158:
159: public function DeleteAllAccountSessions($oAccount)
160: {
161: if ($oAccount instanceof \Aurora\System\Classes\Account) {
162: $iAccountId = $oAccount->Id;
163: $sAccountType = get_class($oAccount);
164: if (\Aurora\Api::GetSettings()->GetValue('StoreAuthTokenInDB', false)) {
165: try {
166: AuthToken::where('AccountId', $iAccountId)->where('AccountType', $sAccountType)->delete();
167: } catch (\Aurora\System\Exceptions\DbException $oEx) {
168: // DB is not configured
169: }
170: } else {
171: if (class_exists($sAccountType)) {
172: $oAccount = $sAccountType::where('Id', $iAccountId)->first();
173: if ($oAccount) {
174: $oAccount->setExtendedProp('TokensValidFromTimestamp', time());
175: $oAccount->save();
176: }
177: }
178: }
179: }
180: }
181:
182: public function SetToDB($iUserId, $iAccountId, $sAccountType, $sAuthToken)
183: {
184: $oAuthToken = AuthToken::where('UserId', $iUserId)
185: ->where('AccountId', $iAccountId)
186: ->where('AccountType', $sAccountType)
187: ->where('Token', $sAuthToken)
188: ->first();
189:
190: if (!$oAuthToken) {
191: $oAuthToken = new AuthToken();
192: }
193: $oAuthToken->UserId = $iUserId;
194: $oAuthToken->AccountId = $iAccountId;
195: $oAuthToken->AccountType = $sAccountType;
196: $oAuthToken->Token = $sAuthToken;
197: $oAuthToken->LastUsageDateTime = time();
198:
199: try {
200: $oAuthToken->save();
201: } catch (\Aurora\System\Exceptions\DbException $oEx) {
202: // DB is not configured
203: }
204: }
205:
206: public function GetFromDB($sAuthToken)
207: {
208: if (!isset(self::$aTokensCache[$sAuthToken])) {
209: try {
210: $oAuthToken = AuthToken::firstWhere('Token', $sAuthToken);
211: if ($oAuthToken) {
212: $oAuthToken->LastUsageDateTime = time();
213: $oAuthToken->save();
214: self::$aTokensCache[$sAuthToken] = $oAuthToken;
215: }
216: } catch (\Aurora\System\Exceptions\DbException $oEx) {
217: \Aurora\Api::LogException($oEx);
218: }
219: }
220: return isset(self::$aTokensCache[$sAuthToken]) ? self::$aTokensCache[$sAuthToken] : false;
221: }
222:
223: public function DeleteFromDB($sAuthToken)
224: {
225: AuthToken::where('Token', $sAuthToken)->delete();
226: }
227:
228: public function GetExpiredAuthTokens($iDays)
229: {
230: $iTime = $iDays * 86400;
231: return AuthToken::query()->whereRaw('(LastUsageDateTime + ' . $iTime . ') < UNIX_TIMESTAMP()')->get();
232: }
233:
234: public function DeleteExpiredAuthTokens($iDays)
235: {
236: $iTime = $iDays * 86400;
237: return AuthToken::query()->whereRaw('(LastUsageDateTime + ' . $iTime . ') < UNIX_TIMESTAMP()')->delete();
238: }
239:
240: public function GetUserSessionsFromDB($iUserId)
241: {
242: return AuthToken::where('UserId', $iUserId)->get();
243: }
244: }
245: