1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* EGroupware API: session handling |
4
|
|
|
* |
5
|
|
|
* This class is based on the old phpgwapi/inc/class.sessions(_php4).inc.php: |
6
|
|
|
* (c) 1998-2000 NetUSE AG Boris Erdmann, Kristian Koehntopp |
7
|
|
|
* (c) 2003 FreeSoftware Foundation |
8
|
|
|
* Not sure how much the current code still has to do with it. |
9
|
|
|
* |
10
|
|
|
* Former authers were: |
11
|
|
|
* - NetUSE AG Boris Erdmann, Kristian Koehntopp |
12
|
|
|
* - Dan Kuykendall <[email protected]> |
13
|
|
|
* - Joseph Engo <[email protected]> |
14
|
|
|
* |
15
|
|
|
* @link http://www.egroupware.org |
16
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
17
|
|
|
* @package api |
18
|
|
|
* @subpackage session |
19
|
|
|
* @author Ralf Becker <[email protected]> since 2003 on |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
namespace EGroupware\Api; |
23
|
|
|
|
24
|
|
|
use PragmaRX\Google2FA; |
25
|
|
|
use EGroupware\Api\Mail\Credentials; |
26
|
|
|
use EGroupware\OpenID; |
27
|
|
|
use League\OAuth2\Server\Exception\OAuthServerException; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Create, verifies or destroys an EGroupware session |
31
|
|
|
* |
32
|
|
|
* If you want to analyse the memory usage in the session, you can uncomment the following call: |
33
|
|
|
* |
34
|
|
|
* static function encrypt($kp3) |
35
|
|
|
* { |
36
|
|
|
* // switch that on to analyse memory usage in the session |
37
|
|
|
* //self::log_session_usage($_SESSION[self::EGW_APPSESSION_VAR],'_SESSION['.self::EGW_APPSESSION_VAR.']',true,5000); |
38
|
|
|
*/ |
39
|
|
|
class Session |
40
|
|
|
{ |
41
|
|
|
/** |
42
|
|
|
* Write debug messages about session verification and creation to the error_log |
43
|
|
|
* |
44
|
|
|
* This will contain passwords! Don't leave it permanently switched on! |
45
|
|
|
*/ |
46
|
|
|
const ERROR_LOG_DEBUG = false; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* key of eGW's session-data in $_SESSION |
50
|
|
|
*/ |
51
|
|
|
const EGW_SESSION_VAR = 'egw_session'; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* key of eGW's application session-data in $_SESSION |
55
|
|
|
*/ |
56
|
|
|
const EGW_APPSESSION_VAR = 'egw_app_session'; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* key of eGW's required files in $_SESSION |
60
|
|
|
* |
61
|
|
|
* These files get set by Db and Egw class, for classes which get not autoloaded (eg. ADOdb, idots_framework) |
62
|
|
|
*/ |
63
|
|
|
const EGW_REQUIRED_FILES = 'egw_required_files'; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* key of eGW's egw_info cached in $_SESSION |
67
|
|
|
*/ |
68
|
|
|
const EGW_INFO_CACHE = 'egw_info_cache'; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* key of eGW's egw object cached in $_SESSION |
72
|
|
|
*/ |
73
|
|
|
const EGW_OBJECT_CACHE = 'egw_object_cache'; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Name of cookie or get-parameter with session-id |
77
|
|
|
*/ |
78
|
|
|
const EGW_SESSION_NAME = 'sessionid'; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Name of cookie with remember me token |
82
|
|
|
*/ |
83
|
|
|
const REMEMBER_ME_COOKIE = 'eGW_remember'; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* current user login (account_lid@domain) |
87
|
|
|
* |
88
|
|
|
* @var string |
89
|
|
|
*/ |
90
|
|
|
var $login; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* current user password |
94
|
|
|
* |
95
|
|
|
* @var string |
96
|
|
|
*/ |
97
|
|
|
var $passwd; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* current user db/ldap account id |
101
|
|
|
* |
102
|
|
|
* @var int |
103
|
|
|
*/ |
104
|
|
|
var $account_id; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* current user account login id (without the eGW-domain/-instance part |
108
|
|
|
* |
109
|
|
|
* @var string |
110
|
|
|
*/ |
111
|
|
|
var $account_lid; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* domain for current user |
115
|
|
|
* |
116
|
|
|
* @var string |
117
|
|
|
*/ |
118
|
|
|
var $account_domain; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* type flag, A - anonymous session, N - None, normal session |
122
|
|
|
* |
123
|
|
|
* @var string |
124
|
|
|
*/ |
125
|
|
|
var $session_flags; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* current user session id |
129
|
|
|
* |
130
|
|
|
* @var string |
131
|
|
|
*/ |
132
|
|
|
var $sessionid; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* an other session specific id (md5 from a random string), |
136
|
|
|
* used together with the sessionid for xmlrpc basic auth and the encryption of session-data (if that's enabled) |
137
|
|
|
* |
138
|
|
|
* @var string |
139
|
|
|
*/ |
140
|
|
|
var $kp3; |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Primary key of egw_access_log row for updates |
144
|
|
|
* |
145
|
|
|
* @var int |
146
|
|
|
*/ |
147
|
|
|
var $sessionid_access_log; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* name of XML-RPC/SOAP method called |
151
|
|
|
* |
152
|
|
|
* @var string |
153
|
|
|
*/ |
154
|
|
|
var $xmlrpc_method_called; |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Array with the name of the system domains |
158
|
|
|
* |
159
|
|
|
* @var array |
160
|
|
|
*/ |
161
|
|
|
private $egw_domains; |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* $_SESSION at the time the constructor was called |
165
|
|
|
* |
166
|
|
|
* @var array |
167
|
|
|
*/ |
168
|
|
|
var $required_files; |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Nummeric code why session creation failed |
172
|
|
|
* |
173
|
|
|
* @var int |
174
|
|
|
*/ |
175
|
|
|
var $cd_reason; |
176
|
|
|
const CD_BAD_LOGIN_OR_PASSWORD = 5; |
177
|
|
|
const CD_SECOND_FACTOR_REQUIRED = 96; |
178
|
|
|
const CD_FORCE_PASSWORD_CHANGE = 97; |
179
|
|
|
const CD_ACCOUNT_EXPIRED = 98; |
180
|
|
|
const CD_BLOCKED = 99; // to many failed attempts to loing |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Verbose reason why session creation failed |
184
|
|
|
* |
185
|
|
|
* @var string |
186
|
|
|
*/ |
187
|
|
|
var $reason; |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Session action set by update_dla or set_action and stored in __destruct |
191
|
|
|
* |
192
|
|
|
* @var string |
193
|
|
|
*/ |
194
|
|
|
protected $action; |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Constructor just loads up some defaults from cookies |
198
|
|
|
* |
199
|
|
|
* @param array $domain_names =null domain-names used in this install |
200
|
|
|
*/ |
201
|
|
|
function __construct(array $domain_names=null) |
202
|
|
|
{ |
203
|
|
|
$this->required_files = $_SESSION[self::EGW_REQUIRED_FILES]; |
204
|
|
|
|
205
|
|
|
$this->sessionid = self::get_sessionid(); |
206
|
|
|
$this->kp3 = self::get_request('kp3'); |
207
|
|
|
|
208
|
|
|
$this->egw_domains = $domain_names; |
209
|
|
|
|
210
|
|
|
if (!isset($GLOBALS['egw_setup'])) |
211
|
|
|
{ |
212
|
|
|
// verfiy and if necessary create and save our config settings |
213
|
|
|
// |
214
|
|
|
$save_rep = false; |
215
|
|
|
if (!isset($GLOBALS['egw_info']['server']['max_access_log_age'])) |
216
|
|
|
{ |
217
|
|
|
$GLOBALS['egw_info']['server']['max_access_log_age'] = 90; // default 90 days |
218
|
|
|
$save_rep = true; |
219
|
|
|
} |
220
|
|
|
if (!isset($GLOBALS['egw_info']['server']['block_time'])) |
221
|
|
|
{ |
222
|
|
|
$GLOBALS['egw_info']['server']['block_time'] = 1; // default 1min, its enough to slow down brute force attacks |
223
|
|
|
$save_rep = true; |
224
|
|
|
} |
225
|
|
|
if (!isset($GLOBALS['egw_info']['server']['num_unsuccessful_id'])) |
226
|
|
|
{ |
227
|
|
|
$GLOBALS['egw_info']['server']['num_unsuccessful_id'] = 3; // default 3 trys per id |
228
|
|
|
$save_rep = true; |
229
|
|
|
} |
230
|
|
|
if (!isset($GLOBALS['egw_info']['server']['num_unsuccessful_ip'])) |
231
|
|
|
{ |
232
|
|
|
$GLOBALS['egw_info']['server']['num_unsuccessful_ip'] = $GLOBALS['egw_info']['server']['num_unsuccessful_id'] * 5; // default is 5 times as high as the id default; since accessing via proxy is quite common |
233
|
|
|
$save_rep = true; |
234
|
|
|
} |
235
|
|
|
if (!isset($GLOBALS['egw_info']['server']['install_id'])) |
236
|
|
|
{ |
237
|
|
|
$GLOBALS['egw_info']['server']['install_id'] = md5(Auth::randomstring(15)); |
238
|
|
|
} |
239
|
|
|
if (!isset($GLOBALS['egw_info']['server']['max_history'])) |
240
|
|
|
{ |
241
|
|
|
$GLOBALS['egw_info']['server']['max_history'] = 20; |
242
|
|
|
$save_rep = true; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if ($save_rep) |
246
|
|
|
{ |
247
|
|
|
$config = new Config('phpgwapi'); |
248
|
|
|
$config->read_repository(); |
249
|
|
|
$config->value('max_access_log_age',$GLOBALS['egw_info']['server']['max_access_log_age']); |
250
|
|
|
$config->value('block_time',$GLOBALS['egw_info']['server']['block_time']); |
251
|
|
|
$config->value('num_unsuccessful_id',$GLOBALS['egw_info']['server']['num_unsuccessful_id']); |
252
|
|
|
$config->value('num_unsuccessful_ip',$GLOBALS['egw_info']['server']['num_unsuccessful_ip']); |
253
|
|
|
$config->value('install_id',$GLOBALS['egw_info']['server']['install_id']); |
254
|
|
|
$config->value('max_history',$GLOBALS['egw_info']['server']['max_history']); |
255
|
|
|
try |
256
|
|
|
{ |
257
|
|
|
$config->save_repository(); |
258
|
|
|
} |
259
|
|
|
catch (Db\Exception $e) { |
260
|
|
|
_egw_log_exception($e); // ignore exception, as it blocks session creation, if database is not writable |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
self::set_cookiedomain(); |
265
|
|
|
|
266
|
|
|
// set session_timeout from global php.ini and default to 14400=4h, if not set |
267
|
|
|
if (!($GLOBALS['egw_info']['server']['sessions_timeout'] = ini_get('session.gc_maxlifetime'))) |
268
|
|
|
{ |
269
|
|
|
ini_set('session.gc_maxlifetime', $GLOBALS['egw_info']['server']['sessions_timeout']=14400); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Magic function called when this class get's restored from the session |
275
|
|
|
* |
276
|
|
|
*/ |
277
|
|
|
function __wakeup() |
278
|
|
|
{ |
279
|
|
|
if (!empty($GLOBALS['egw_info']['server']['sessions_timeout']) && session_status() === PHP_SESSION_NONE) |
280
|
|
|
{ |
281
|
|
|
ini_set('session.gc_maxlifetime', $GLOBALS['egw_info']['server']['sessions_timeout']); |
282
|
|
|
} |
283
|
|
|
$this->action = null; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Destructor: update access-log and encrypt session |
288
|
|
|
*/ |
289
|
|
|
function __destruct() |
290
|
|
|
{ |
291
|
|
|
self::encrypt($this->kp3); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* commit the sessiondata to storage |
296
|
|
|
* |
297
|
|
|
* It's necessary to use this function instead of session_write_close() direct, as otherwise the session is not encrypted! |
298
|
|
|
*/ |
299
|
|
|
function commit_session() |
300
|
|
|
{ |
301
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() sessionid=$this->sessionid, _SESSION[".self::EGW_SESSION_VAR.']='.array2string($_SESSION[self::EGW_SESSION_VAR]).' '.function_backtrace()); |
302
|
|
|
self::encrypt($this->kp3); |
303
|
|
|
|
304
|
|
|
session_write_close(); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* Keys of session variables which get encrypted |
309
|
|
|
* |
310
|
|
|
* @var array |
311
|
|
|
*/ |
312
|
|
|
static $egw_session_vars = array( |
313
|
|
|
//self::EGW_SESSION_VAR, no need to encrypt and required by the session list |
314
|
|
|
self::EGW_APPSESSION_VAR, |
315
|
|
|
self::EGW_INFO_CACHE, |
316
|
|
|
self::EGW_OBJECT_CACHE, |
317
|
|
|
); |
318
|
|
|
|
319
|
|
|
static $mcrypt; |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Name of flag in session to signal it is encrypted or not |
323
|
|
|
*/ |
324
|
|
|
const EGW_SESSION_ENCRYPTED = 'egw_session_encrypted'; |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Encrypt the variables in the session |
328
|
|
|
* |
329
|
|
|
* Is called by self::__destruct(). |
330
|
|
|
*/ |
331
|
|
|
static function encrypt($kp3) |
332
|
|
|
{ |
333
|
|
|
// switch that on to analyse memory usage in the session |
334
|
|
|
//self::log_session_usage($_SESSION[self::EGW_APPSESSION_VAR],'_SESSION['.self::EGW_APPSESSION_VAR.']',true,5000); |
335
|
|
|
|
336
|
|
|
if (!isset($_SESSION[self::EGW_SESSION_ENCRYPTED]) && self::init_crypt($kp3)) |
337
|
|
|
{ |
338
|
|
|
foreach(self::$egw_session_vars as $name) |
339
|
|
|
{ |
340
|
|
|
if (isset($_SESSION[$name])) |
341
|
|
|
{ |
342
|
|
|
$_SESSION[$name] = mcrypt_generic(self::$mcrypt,serialize($_SESSION[$name])); |
|
|
|
|
343
|
|
|
//error_log(__METHOD__."() 'encrypting' session var: $name, len=".strlen($_SESSION[$name])); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
$_SESSION[self::EGW_SESSION_ENCRYPTED] = true; // flag session as encrypted |
347
|
|
|
|
348
|
|
|
mcrypt_generic_deinit(self::$mcrypt); |
|
|
|
|
349
|
|
|
self::$mcrypt = null; |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Log the usage of session-vars |
355
|
|
|
* |
356
|
|
|
* @param array &$arr |
357
|
|
|
* @param string $label |
358
|
|
|
* @param boolean $recursion =true if true call itself for every item > $limit |
359
|
|
|
* @param int $limit =1000 log only differences > $limit |
360
|
|
|
*/ |
361
|
|
|
static function log_session_usage(&$arr,$label,$recursion=true,$limit=1000) |
362
|
|
|
{ |
363
|
|
|
if (!is_array($arr)) return; |
|
|
|
|
364
|
|
|
|
365
|
|
|
$sizes = array(); |
366
|
|
|
foreach($arr as $key => &$data) |
367
|
|
|
{ |
368
|
|
|
$sizes[$key] = strlen(serialize($data)); |
369
|
|
|
} |
370
|
|
|
arsort($sizes,SORT_NUMERIC); |
371
|
|
|
foreach($sizes as $key => $size) |
372
|
|
|
{ |
373
|
|
|
$diff = $size - (int)$_SESSION[$label.'-sizes'][$key]; |
374
|
|
|
$_SESSION[$label.'-sizes'][$key] = $size; |
375
|
|
|
if ($diff > $limit) |
376
|
|
|
{ |
377
|
|
|
error_log("strlen({$label}[$key])=".Vfs::hsize($size).", diff=".Vfs::hsize($diff)); |
378
|
|
|
if ($recursion) self::log_session_usage($arr[$key],$label.'['.$key.']',$recursion,$limit); |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Decrypt the variables in the session |
385
|
|
|
* |
386
|
|
|
* Is called by self::init_handler from api/src/loader.php (called from the header.inc.php) |
387
|
|
|
* before the restore of the eGW enviroment takes place, so that the whole thing can be encrypted |
388
|
|
|
*/ |
389
|
|
|
static function decrypt() |
390
|
|
|
{ |
391
|
|
|
if ($_SESSION[self::EGW_SESSION_ENCRYPTED] && self::init_crypt(self::get_request('kp3'))) |
392
|
|
|
{ |
393
|
|
|
foreach(self::$egw_session_vars as $name) |
394
|
|
|
{ |
395
|
|
|
if (isset($_SESSION[$name])) |
396
|
|
|
{ |
397
|
|
|
$_SESSION[$name] = unserialize(trim(mdecrypt_generic(self::$mcrypt,$_SESSION[$name]))); |
|
|
|
|
398
|
|
|
//error_log(__METHOD__."() 'decrypting' session var $name: gettype($name) = ".gettype($_SESSION[$name])); |
399
|
|
|
} |
400
|
|
|
} |
401
|
|
|
unset($_SESSION[self::EGW_SESSION_ENCRYPTED]); // delete encryption flag |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* Check if session encryption is configured, possible and initialise it |
407
|
|
|
* |
408
|
|
|
* If mcrypt extension is not available (eg. in PHP 7.2+ no longer contains it) fail gracefully. |
409
|
|
|
* |
410
|
|
|
* @param string $kp3 mcrypt key transported via cookie or get parameter like the session id, |
411
|
|
|
* unlike the session id it's not know on the server, so only the client-request can decrypt the session! |
412
|
|
|
* @return boolean true if encryption is used, false otherwise |
413
|
|
|
*/ |
414
|
|
|
static private function init_crypt($kp3) |
415
|
|
|
{ |
416
|
|
|
if(!$GLOBALS['egw_info']['server']['mcrypt_enabled']) |
417
|
|
|
{ |
418
|
|
|
return false; // session encryption is switched off |
419
|
|
|
} |
420
|
|
|
if ($GLOBALS['egw_info']['currentapp'] == 'syncml' || !$kp3) |
421
|
|
|
{ |
422
|
|
|
$kp3 = 'staticsyncmlkp3'; // syncml has no kp3! |
423
|
|
|
} |
424
|
|
|
if (is_null(self::$mcrypt)) |
425
|
|
|
{ |
426
|
|
|
if (!check_load_extension('mcrypt')) |
427
|
|
|
{ |
428
|
|
|
error_log(__METHOD__."() required PHP extension mcrypt not loaded and can not be loaded, sessions get NOT encrypted!"); |
429
|
|
|
return false; |
430
|
|
|
} |
431
|
|
|
if (!(self::$mcrypt = mcrypt_module_open(MCRYPT_TRIPLEDES, '', MCRYPT_MODE_ECB, ''))) |
|
|
|
|
432
|
|
|
{ |
433
|
|
|
error_log(__METHOD__."() could not mcrypt_module_open(MCRYPT_TRIPLEDES,'',MCRYPT_MODE_ECB,''), sessions get NOT encrypted!"); |
434
|
|
|
return false; |
435
|
|
|
} |
436
|
|
|
$iv_size = mcrypt_enc_get_iv_size(self::$mcrypt); |
|
|
|
|
437
|
|
|
$iv = !isset($GLOBALS['egw_info']['server']['mcrypt_iv']) || strlen($GLOBALS['egw_info']['server']['mcrypt_iv']) < $iv_size ? |
438
|
|
|
mcrypt_create_iv ($iv_size, MCRYPT_RAND) : substr($GLOBALS['egw_info']['server']['mcrypt_iv'],0,$iv_size); |
|
|
|
|
439
|
|
|
|
440
|
|
|
if (mcrypt_generic_init(self::$mcrypt,$kp3, $iv) < 0) |
|
|
|
|
441
|
|
|
{ |
442
|
|
|
error_log(__METHOD__."() could not initialise mcrypt, sessions get NOT encrypted!"); |
443
|
|
|
return self::$mcrypt = false; |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
return is_resource(self::$mcrypt); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Create a new eGW session |
451
|
|
|
* |
452
|
|
|
* @param string $login user login |
453
|
|
|
* @param string $passwd user password |
454
|
|
|
* @param string $passwd_type type of password being used, ie plaintext, md5, sha1 |
455
|
|
|
* @param boolean $no_session =false dont create a real session, eg. for GroupDAV clients using only basic auth, no cookie support |
456
|
|
|
* @param boolean $auth_check =true if false, the user is loged in without checking his password (eg. for single sign on), default = true |
457
|
|
|
* @param boolean $fail_on_forced_password_change =false true: do NOT create session, if password change requested |
458
|
|
|
* @param string|boolean $check_2fa =false string: 2fa-code to check (only if exists) and fail if wrong, false: do NOT check 2fa |
459
|
|
|
* @param string $remember_me =null "True" for checkbox checked, or periode for user-choice select-box eg. "P1W" or "" for NOT remember |
460
|
|
|
* @return string|boolean session id or false if session was not created, $this->(cd_)reason contains cause |
461
|
|
|
*/ |
462
|
|
|
function create($login,$passwd = '',$passwd_type = '',$no_session=false,$auth_check=true,$fail_on_forced_password_change=false,$check_2fa=false,$remember_me=null) |
463
|
|
|
{ |
464
|
|
|
try { |
465
|
|
|
if (is_array($login)) |
|
|
|
|
466
|
|
|
{ |
467
|
|
|
$this->login = $login['login']; |
468
|
|
|
$this->passwd = $login['passwd']; |
469
|
|
|
$this->passwd_type = $login['passwd_type']; |
470
|
|
|
$login = $this->login; |
471
|
|
|
} |
472
|
|
|
else |
473
|
|
|
{ |
474
|
|
|
$this->login = $login; |
475
|
|
|
$this->passwd = $passwd; |
476
|
|
|
$this->passwd_type = $passwd_type; |
|
|
|
|
477
|
|
|
} |
478
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) starting ..."); |
479
|
|
|
|
480
|
|
|
self::split_login_domain($login,$this->account_lid,$this->account_domain); |
|
|
|
|
481
|
|
|
// add domain to the login, if not already there |
482
|
|
|
if (substr($this->login,-strlen($this->account_domain)-1) != '@'.$this->account_domain) |
483
|
|
|
{ |
484
|
|
|
$this->login .= '@'.$this->account_domain; |
485
|
|
|
} |
486
|
|
|
$now = time(); |
487
|
|
|
//error_log(__METHOD__."($login,$passwd,$passwd_type,$no_session,$auth_check) account_lid=$this->account_lid, account_domain=$this->account_domain, default_domain={$GLOBALS['egw_info']['server']['default_domain']}, user/domain={$GLOBALS['egw_info']['user']['domain']}"); |
488
|
|
|
|
489
|
|
|
// This is to ensure that we authenticate to the correct domain (might not be default) |
490
|
|
|
// if no domain is given we use the default domain, so we dont need to re-create everything |
491
|
|
|
if (!$GLOBALS['egw_info']['user']['domain'] && $this->account_domain == $GLOBALS['egw_info']['server']['default_domain']) |
492
|
|
|
{ |
493
|
|
|
$GLOBALS['egw_info']['user']['domain'] = $this->account_domain; |
494
|
|
|
} |
495
|
|
|
elseif (!$this->account_domain && $GLOBALS['egw_info']['user']['domain']) |
496
|
|
|
{ |
497
|
|
|
$this->account_domain = $GLOBALS['egw_info']['user']['domain']; |
498
|
|
|
} |
499
|
|
|
elseif($this->account_domain != $GLOBALS['egw_info']['user']['domain']) |
500
|
|
|
{ |
501
|
|
|
throw new Exception("Wrong domain! '$this->account_domain' != '{$GLOBALS['egw_info']['user']['domain']}'"); |
502
|
|
|
} |
503
|
|
|
unset($GLOBALS['egw_info']['server']['default_domain']); // we kill this for security reasons |
504
|
|
|
|
505
|
|
|
$user_ip = self::getuser_ip(); |
506
|
|
|
|
507
|
|
|
$this->account_id = $GLOBALS['egw']->accounts->name2id($this->account_lid,'account_lid','u'); |
508
|
|
|
|
509
|
|
|
// do we need to check 'remember me' token (to bypass authentication) |
510
|
|
|
if ($auth_check && !empty($_COOKIE[self::REMEMBER_ME_COOKIE])) |
511
|
|
|
{ |
512
|
|
|
$auth_check = !$this->skipPasswordAuth($_COOKIE[self::REMEMBER_ME_COOKIE], $this->account_id); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
if (($blocked = $this->login_blocked($login,$user_ip)) || // too many unsuccessful attempts |
516
|
|
|
$GLOBALS['egw_info']['server']['global_denied_users'][$this->account_lid] || |
517
|
|
|
$auth_check && !$GLOBALS['egw']->auth->authenticate($this->account_lid, $this->passwd, $this->passwd_type) || |
518
|
|
|
$this->account_id && $GLOBALS['egw']->accounts->get_type($this->account_id) == 'g') |
519
|
|
|
{ |
520
|
|
|
$this->reason = $blocked ? 'blocked, too many attempts' : 'bad login or password'; |
521
|
|
|
$this->cd_reason = $blocked ? self::CD_BLOCKED : self::CD_BAD_LOGIN_OR_PASSWORD; |
522
|
|
|
|
523
|
|
|
// we dont log anon users as it would block the website |
524
|
|
|
if (!$GLOBALS['egw']->acl->get_specific_rights_for_account($this->account_id,'anonymous','phpgwapi')) |
525
|
|
|
{ |
526
|
|
|
$this->log_access($this->reason,$login,$user_ip,0); // log unsuccessfull login |
527
|
|
|
} |
528
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) UNSUCCESSFULL ($this->reason)"); |
529
|
|
|
return false; |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
if (!$this->account_id && $GLOBALS['egw_info']['server']['auto_create_acct']) |
533
|
|
|
{ |
534
|
|
|
if ($GLOBALS['egw_info']['server']['auto_create_acct'] == 'lowercase') |
535
|
|
|
{ |
536
|
|
|
$this->account_lid = strtolower($this->account_lid); |
537
|
|
|
} |
538
|
|
|
$this->account_id = $GLOBALS['egw']->accounts->auto_add($this->account_lid, $passwd); |
539
|
|
|
} |
540
|
|
|
// fix maybe wrong case in username, it makes problems eg. in filemanager (name of homedir) |
541
|
|
|
if ($this->account_lid != ($lid = $GLOBALS['egw']->accounts->id2name($this->account_id))) |
542
|
|
|
{ |
543
|
|
|
$this->account_lid = $lid; |
544
|
|
|
$this->login = $lid.substr($this->login,strlen($lid)); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
$GLOBALS['egw_info']['user']['account_id'] = $this->account_id; |
548
|
|
|
|
549
|
|
|
// for *DAV and eSync we use a pseudo sessionid created from md5(user:passwd) |
550
|
|
|
// --> allows this stateless protocolls which use basic auth to use sessions! |
551
|
|
|
if (($this->sessionid = self::get_sessionid(true))) |
552
|
|
|
{ |
553
|
|
|
session_id($this->sessionid); |
554
|
|
|
} |
555
|
|
|
else |
556
|
|
|
{ |
557
|
|
|
self::cache_control(); |
558
|
|
|
session_start(); |
559
|
|
|
// set a new session-id, if not syncml (already done in Horde code and can NOT be changed) |
560
|
|
|
if (!$no_session && $GLOBALS['egw_info']['flags']['currentapp'] != 'syncml') |
561
|
|
|
{ |
562
|
|
|
session_regenerate_id(true); |
563
|
|
|
} |
564
|
|
|
$this->sessionid = session_id(); |
565
|
|
|
} |
566
|
|
|
$this->kp3 = Auth::randomstring(24); |
567
|
|
|
|
568
|
|
|
$GLOBALS['egw_info']['user'] = $this->read_repositories(); |
569
|
|
|
if ($GLOBALS['egw']->accounts->is_expired($GLOBALS['egw_info']['user'])) |
570
|
|
|
{ |
571
|
|
|
$this->reason = 'account is expired'; |
572
|
|
|
$this->cd_reason = self::CD_ACCOUNT_EXPIRED; |
573
|
|
|
|
574
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) UNSUCCESSFULL ($this->reason)"); |
575
|
|
|
return false; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
Cache::setSession('phpgwapi', 'password', base64_encode($this->passwd)); |
579
|
|
|
|
580
|
|
|
// if we have a second factor, check it before forced password change |
581
|
|
|
if ($check_2fa !== false) |
582
|
|
|
{ |
583
|
|
|
try { |
584
|
|
|
$this->checkMultifactorAuth($check_2fa, $_COOKIE[self::REMEMBER_ME_COOKIE]); |
585
|
|
|
} |
586
|
|
|
catch(\Exception $e) { |
587
|
|
|
$this->cd_reason = $e->getCode(); |
588
|
|
|
$this->reason = $e->getMessage(); |
589
|
|
|
$this->log_access($this->reason, $login, $user_ip, 0); // log unsuccessfull login |
590
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check,$fail_on_forced_password_change,'$check_2fa') UNSUCCESSFULL ($this->reason)"); |
591
|
|
|
return false; |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
if ($fail_on_forced_password_change && Auth::check_password_change($this->reason) === false) |
596
|
|
|
{ |
597
|
|
|
$this->cd_reason = self::CD_FORCE_PASSWORD_CHANGE; |
598
|
|
|
return false; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
if ($GLOBALS['egw']->acl->check('anonymous',1,'phpgwapi')) |
602
|
|
|
{ |
603
|
|
|
$this->session_flags = 'A'; |
604
|
|
|
} |
605
|
|
|
else |
606
|
|
|
{ |
607
|
|
|
$this->session_flags = 'N'; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
if (($hook_result = Hooks::process(array( |
611
|
|
|
'location' => 'session_creation', |
612
|
|
|
'sessionid' => $this->sessionid, |
613
|
|
|
'session_flags' => $this->session_flags, |
614
|
|
|
'account_id' => $this->account_id, |
615
|
|
|
'account_lid' => $this->account_lid, |
616
|
|
|
'passwd' => $this->passwd, |
617
|
|
|
'account_domain' => $this->account_domain, |
618
|
|
|
'user_ip' => $user_ip, |
619
|
|
|
),'',true))) // true = run hooks from all apps, not just the ones the current user has perms to run |
620
|
|
|
{ |
621
|
|
|
foreach($hook_result as $reason) |
622
|
|
|
{ |
623
|
|
|
if ($reason) // called hook requests to deny the session |
624
|
|
|
{ |
625
|
|
|
$this->reason = $this->cd_reason = $reason; |
626
|
|
|
$this->log_access($this->reason,$login,$user_ip,0); // log unsuccessfull login |
627
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) UNSUCCESSFULL ($this->reason)"); |
628
|
|
|
return false; |
629
|
|
|
} |
630
|
|
|
} |
631
|
|
|
} |
632
|
|
|
$GLOBALS['egw']->db->transaction_begin(); |
633
|
|
|
$this->register_session($this->login,$user_ip,$now,$this->session_flags); |
634
|
|
|
if ($this->session_flags != 'A') // dont log anonymous sessions |
635
|
|
|
{ |
636
|
|
|
$this->sessionid_access_log = $this->log_access($this->sessionid,$login,$user_ip,$this->account_id); |
637
|
|
|
// We do NOT log anonymous sessions to not block website and also to cope with |
638
|
|
|
// high rate anon endpoints might be called creating a bottleneck in the egw_accounts table. |
639
|
|
|
Cache::setSession('phpgwapi', 'account_previous_login', $GLOBALS['egw']->auth->previous_login); |
640
|
|
|
$GLOBALS['egw']->accounts->update_lastlogin($this->account_id,$user_ip); |
641
|
|
|
} |
642
|
|
|
$GLOBALS['egw']->db->transaction_commit(); |
643
|
|
|
|
644
|
|
|
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session) |
645
|
|
|
{ |
646
|
|
|
self::egw_setcookie(self::EGW_SESSION_NAME,$this->sessionid); |
647
|
|
|
self::egw_setcookie('kp3',$this->kp3); |
648
|
|
|
self::egw_setcookie('domain',$this->account_domain); |
649
|
|
|
} |
650
|
|
|
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session || isset($_COOKIE['last_loginid'])) |
|
|
|
|
651
|
|
|
{ |
652
|
|
|
self::egw_setcookie('last_loginid', $this->account_lid ,$now+1209600); /* For 2 weeks */ |
653
|
|
|
self::egw_setcookie('last_domain',$this->account_domain,$now+1209600); |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
// set new remember me token/cookie, if requested and necessary |
657
|
|
|
$expiration = null; |
658
|
|
|
if (($token = $this->checkSetRememberMeToken($remember_me, $_COOKIE[self::REMEMBER_ME_COOKIE], $expiration))) |
659
|
|
|
{ |
660
|
|
|
self::egw_setcookie(self::REMEMBER_ME_COOKIE, $token, $expiration); |
661
|
|
|
} |
662
|
|
|
|
663
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) successfull sessionid=$this->sessionid"); |
664
|
|
|
|
665
|
|
|
// hook called once session is created |
666
|
|
|
Hooks::process(array( |
667
|
|
|
'location' => 'session_created', |
668
|
|
|
'sessionid' => $this->sessionid, |
669
|
|
|
'session_flags' => $this->session_flags, |
670
|
|
|
'account_id' => $this->account_id, |
671
|
|
|
'account_lid' => $this->account_lid, |
672
|
|
|
'passwd' => $this->passwd, |
673
|
|
|
'account_domain' => $this->account_domain, |
674
|
|
|
'user_ip' => $user_ip, |
675
|
|
|
'session_type' => Session\Type::get($_SERVER['REQUEST_URI'], |
676
|
|
|
$GLOBALS['egw_info']['flags']['current_app'], |
677
|
|
|
true), // true return WebGUI instead of login, as we are logged in now |
678
|
|
|
),'',true); |
679
|
|
|
|
680
|
|
|
return $this->sessionid; |
681
|
|
|
} |
682
|
|
|
// catch all exceptions, as their (allways logged) trace (eg. on a database error) would contain the user password |
683
|
|
|
catch(Exception $e) { |
684
|
|
|
$this->reason = $this->cd_reason = is_a($e, Db\Exception::class) ? |
|
|
|
|
685
|
|
|
// do not output specific database error, eg. invalid SQL statement |
686
|
|
|
lang('Database Error!') : $e->getMessage(); |
687
|
|
|
error_log(__METHOD__."('$login', ".array2string(str_repeat('*', strlen($passwd))). |
688
|
|
|
", '$passwd_type', no_session=".array2string($no_session). |
689
|
|
|
", auth_check=".array2string($auth_check). |
690
|
|
|
", fail_on_forced_password_change=".array2string($fail_on_forced_password_change). |
691
|
|
|
") Exception ".$e->getMessage()); |
692
|
|
|
return false; |
693
|
|
|
} |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Check if password authentication is required or given token is sufficient |
698
|
|
|
* |
699
|
|
|
* Token is only checked for 'remember_me_token' === 'always', not for default of only for 2FA! |
700
|
|
|
* |
701
|
|
|
* Password auth is also required if 2FA is not disabled and either required or configured by user. |
702
|
|
|
* |
703
|
|
|
* @param string $token value of token |
704
|
|
|
* @param int& $account_id =null account_id of token-owner to limit check on that user, on return account_id of token owner |
705
|
|
|
* @return boolean false: if further auth check is required, true: if token is sufficient for authentication |
706
|
|
|
*/ |
707
|
|
|
public function skipPasswordAuth($token, &$account_id=null) |
708
|
|
|
{ |
709
|
|
|
// if token is empty or disabled --> password authentication required |
710
|
|
|
if (empty($token) || $GLOBALS['egw_info']['server']['remember_me_token'] !== 'always' || |
711
|
|
|
!($client = $this->checkOpenIDconfigured())) |
712
|
|
|
{ |
713
|
|
|
return false; |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
// check if token exists and is (still) valid |
717
|
|
|
$tokenRepo = new OpenID\Repositories\AccessTokenRepository(); |
718
|
|
|
if (!($access_token = $tokenRepo->findToken($client, $account_id, 'PT1S', $token))) |
|
|
|
|
719
|
|
|
{ |
720
|
|
|
return false; |
721
|
|
|
} |
722
|
|
|
$account_id = $access_token->getUserIdentifier(); |
723
|
|
|
|
724
|
|
|
// check if we need a second factor |
725
|
|
|
if ($GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' && |
726
|
|
|
(($creds = Credentials::read(0, Credentials::TWOFA, $account_id)) || |
|
|
|
|
727
|
|
|
$GLOBALS['egw_info']['server']['2fa_required'] === 'strict')) |
728
|
|
|
{ |
729
|
|
|
return false; |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
// access-token is sufficient |
733
|
|
|
return true; |
734
|
|
|
} |
735
|
|
|
|
736
|
|
|
/** |
737
|
|
|
* Check multifcator authemtication |
738
|
|
|
* |
739
|
|
|
* @param string $code 2fa-code |
740
|
|
|
* @param string $token remember me token |
741
|
|
|
* @throws \Exception with error-message if NOT successful |
742
|
|
|
*/ |
743
|
|
|
protected function checkMultifactorAuth($code, $token) |
744
|
|
|
{ |
745
|
|
|
$errors = $factors = []; |
746
|
|
|
|
747
|
|
|
if ($GLOBALS['egw_info']['server']['2fa_required'] === 'disabled') |
748
|
|
|
{ |
749
|
|
|
return; // nothing to check |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
// check if token exists and is (still) valid |
753
|
|
|
if (!empty($token) && $GLOBALS['egw_info']['server']['remember_me_token'] !== 'disabled' && |
754
|
|
|
($client = $this->checkOpenIDconfigured())) |
755
|
|
|
{ |
756
|
|
|
$tokenRepo = new OpenID\Repositories\AccessTokenRepository(); |
757
|
|
|
if ($tokenRepo->findToken($client, $this->account_id, 'PT1S', $token)) |
|
|
|
|
758
|
|
|
{ |
759
|
|
|
$factors['remember_me_token'] = true; |
760
|
|
|
} |
761
|
|
|
else |
762
|
|
|
{ |
763
|
|
|
$errors['remember_me_token'] = lang("Invalid or expired 'remember me' token"); |
764
|
|
|
} |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
// if 2fa is configured by user, check it |
768
|
|
|
if (($creds = Credentials::read(0, Credentials::TWOFA, $this->account_id))) |
769
|
|
|
{ |
770
|
|
|
if (empty($code)) |
771
|
|
|
{ |
772
|
|
|
$errors['2fa_code'] = lang('2-Factor Authentication code required'); |
773
|
|
|
} |
774
|
|
|
else |
775
|
|
|
{ |
776
|
|
|
$google2fa = new Google2FA\Google2FA(); |
777
|
|
|
if (!empty($code) && $google2fa->verify($code, $creds['2fa_password'])) |
778
|
|
|
{ |
779
|
|
|
$factors['2fa_code'] = true; |
780
|
|
|
} |
781
|
|
|
else |
782
|
|
|
{ |
783
|
|
|
$errors['2fa_code'] = lang('Invalid 2-Factor Authentication code'); |
784
|
|
|
} |
785
|
|
|
} |
786
|
|
|
} |
787
|
|
|
|
788
|
|
|
// check for more factors and/or policies |
789
|
|
|
// hook can add factors, errors or throw \Exception with error-message and -code |
790
|
|
|
Hooks::process([ |
791
|
|
|
'location' => 'multifactor_policy', |
792
|
|
|
'factors' => &$factors, |
793
|
|
|
'errors' => &$errors, |
794
|
|
|
'2fa_code' => $code, |
795
|
|
|
'remember_me_token' => $token, |
796
|
|
|
], [], true); |
797
|
|
|
|
798
|
|
|
if (!count($factors) && (count($errors) || |
799
|
|
|
$GLOBALS['egw_info']['server']['2fa_required'] === 'strict')) |
800
|
|
|
{ |
801
|
|
|
if (!empty($code) && isset($errors['2fa_code'])) |
802
|
|
|
{ |
803
|
|
|
// we log the missing factor, but externally only show "Bad Login or Password" |
804
|
|
|
// to give no indication that the password was already correct |
805
|
|
|
throw new \Exception(implode(', ', $errors), self::CD_BAD_LOGIN_OR_PASSWORD); |
806
|
|
|
} |
807
|
|
|
else |
808
|
|
|
{ |
809
|
|
|
throw new \Exception(implode(', ', $errors), self::CD_SECOND_FACTOR_REQUIRED); |
810
|
|
|
} |
811
|
|
|
} |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
/** |
815
|
|
|
* Check if we need to set a remember me token/cookie |
816
|
|
|
* |
817
|
|
|
* @param string $remember_me =null "True" for checkbox checked, or periode for user-choice select-box eg. "P1W" or "" for NOT remember |
818
|
|
|
* @param string $token current remember me token |
819
|
|
|
* @param int& $expriation on return expiration time of new cookie |
820
|
|
|
* @return string new token to set as Cookieor null to not set a new one |
821
|
|
|
*/ |
822
|
|
|
protected function checkSetRememberMeToken($remember_me, $token, &$expiration) |
823
|
|
|
{ |
824
|
|
|
// do we need a new token |
825
|
|
|
if (!empty($remember_me) && $GLOBALS['egw_info']['server']['remember_me_token'] !== 'disabled' && |
826
|
|
|
($client = $this->checkOpenIDconfigured())) |
827
|
|
|
{ |
828
|
|
|
if (!empty($token)) |
829
|
|
|
{ |
830
|
|
|
// check if token exists and is (still) valid |
831
|
|
|
$tokenRepo = new OpenID\Repositories\AccessTokenRepository(); |
832
|
|
|
if ($tokenRepo->findToken($client, $this->account_id, 'PT1S', $token)) |
|
|
|
|
833
|
|
|
{ |
834
|
|
|
return null; // token still valid, no need to set it again |
835
|
|
|
} |
836
|
|
|
} |
837
|
|
|
$lifetime = $this->rememberMeTokenLifetime(is_string($remember_me) ? $remember_me : null); |
|
|
|
|
838
|
|
|
$expiration = $this->rememberMeTokenLifetime(is_string($remember_me) ? $remember_me : null, true); |
|
|
|
|
839
|
|
|
|
840
|
|
|
$tokenFactory = new OpenID\Token(); |
841
|
|
|
if (($token = $tokenFactory->accessToken(self::OPENID_REMEMBER_ME_CLIENT_ID, [], $lifetime, false, $lifetime, false))) |
|
|
|
|
842
|
|
|
{ |
843
|
|
|
return $token->getIdentifier(); |
844
|
|
|
} |
845
|
|
|
} |
846
|
|
|
return null; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
/** |
850
|
|
|
* Check if 'remember me' token should be deleted on explict logout |
851
|
|
|
* |
852
|
|
|
* @return boolean false: if 2FA is enabeld for user, true: otherwise |
853
|
|
|
*/ |
854
|
|
|
public function removeRememberMeTokenOnLogout() |
855
|
|
|
{ |
856
|
|
|
return $GLOBALS['egw_info']['server']['2fa_required'] === 'disabled' || |
857
|
|
|
$GLOBALS['egw_info']['server']['2fa_required'] !== 'strict' && |
858
|
|
|
!($creds = Credentials::read(0, Credentials::TWOFA, $this->account_id)); |
|
|
|
|
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
/** |
862
|
|
|
* OpenID Client ID for remember me token |
863
|
|
|
*/ |
864
|
|
|
const OPENID_REMEMBER_ME_CLIENT_ID = 'login-remember-me'; |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* Check and if not configure OpenID app to generate 'remember me' tokens |
868
|
|
|
* |
869
|
|
|
* @return OpenID\Entities\ClientEntity|null null if OpenID Server app is not installed |
870
|
|
|
*/ |
871
|
|
|
protected function checkOpenIDconfigured() |
872
|
|
|
{ |
873
|
|
|
// OpenID app not installed --> password authentication required |
874
|
|
|
if (!isset($GLOBALS['egw_info']['apps'])) |
875
|
|
|
{ |
876
|
|
|
$GLOBALS['egw']->applications->read_installed_apps(); |
877
|
|
|
} |
878
|
|
|
if (empty($GLOBALS['egw_info']['apps']['openid'])) |
879
|
|
|
{ |
880
|
|
|
return null; |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
$clients = new OpenID\Repositories\ClientRepository(); |
884
|
|
|
try { |
885
|
|
|
$client = $clients->getClientEntity(self::OPENID_REMEMBER_ME_CLIENT_ID, null, null, false); // false = do NOT check client-secret |
886
|
|
|
} |
887
|
|
|
catch (OAuthServerException $e) |
888
|
|
|
{ |
889
|
|
|
unset($e); |
890
|
|
|
$client = new OpenID\Entities\ClientEntity(); |
891
|
|
|
$client->setIdentifier(self::OPENID_REMEMBER_ME_CLIENT_ID); |
892
|
|
|
$client->setSecret(Auth::randomstring(24)); // must not be unset |
|
|
|
|
893
|
|
|
$client->setName(lang('Remember me token')); |
894
|
|
|
$client->setAccessTokenTTL($this->rememberMeTokenLifetime()); |
895
|
|
|
$client->setRefreshTokenTTL('P0S'); // no refresh token |
896
|
|
|
$client->setRedirectUri($GLOBALS['egw_info']['server']['webserver_url'].'/'); |
897
|
|
|
$clients->persistNewClient($client); |
|
|
|
|
898
|
|
|
} |
899
|
|
|
return $client; |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
/** |
903
|
|
|
* Return lifetime for remember me token |
904
|
|
|
* |
905
|
|
|
* @param string $user user choice, if allowed |
906
|
|
|
* @param boolean $ts =false false: return periode string, true: return integer timestamp |
907
|
|
|
* @return string periode spec eg. 'P1M' |
908
|
|
|
*/ |
909
|
|
|
protected function rememberMeTokenLifetime($user=null, $ts=false) |
910
|
|
|
{ |
911
|
|
|
switch ((string)$GLOBALS['egw_info']['server']['remember_me_lifetime']) |
912
|
|
|
{ |
913
|
|
|
case 'user': |
914
|
|
|
if (!empty($user)) |
915
|
|
|
{ |
916
|
|
|
$lifetime = $user; |
917
|
|
|
break; |
918
|
|
|
} |
919
|
|
|
// fall-through for default lifetime |
920
|
|
|
case '': // default lifetime |
921
|
|
|
$lifetime = 'P1M'; |
922
|
|
|
break; |
923
|
|
|
default: |
924
|
|
|
$lifetime = $GLOBALS['egw_info']['server']['remember_me_lifetime']; |
925
|
|
|
break; |
926
|
|
|
} |
927
|
|
|
if ($ts) |
928
|
|
|
{ |
929
|
|
|
$expiration = new DateTime('now', DateTime::$server_timezone); |
930
|
|
|
$expiration->add(new \DateInterval($lifetime)); |
931
|
|
|
return $expiration->format('ts'); |
932
|
|
|
} |
933
|
|
|
return $lifetime; |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* Store eGW specific session-vars |
938
|
|
|
* |
939
|
|
|
* @param string $login |
940
|
|
|
* @param string $user_ip |
941
|
|
|
* @param int $now |
942
|
|
|
* @param string $session_flags |
943
|
|
|
*/ |
944
|
|
|
private function register_session($login,$user_ip,$now,$session_flags) |
945
|
|
|
{ |
946
|
|
|
// restore session vars set before session was started |
947
|
|
|
if (is_array($this->required_files)) |
|
|
|
|
948
|
|
|
{ |
949
|
|
|
$_SESSION[self::EGW_REQUIRED_FILES] = !is_array($_SESSION[self::EGW_REQUIRED_FILES]) ? $this->required_files : |
950
|
|
|
array_unique(array_merge($_SESSION[self::EGW_REQUIRED_FILES],$this->required_files)); |
951
|
|
|
unset($this->required_files); |
952
|
|
|
} |
953
|
|
|
$_SESSION[self::EGW_SESSION_VAR] = array( |
954
|
|
|
'session_id' => $this->sessionid, |
955
|
|
|
'session_lid' => $login, |
956
|
|
|
'session_ip' => $user_ip, |
957
|
|
|
'session_logintime' => $now, |
958
|
|
|
'session_dla' => $now, |
959
|
|
|
'session_action' => $_SERVER['PHP_SELF'], |
960
|
|
|
'session_flags' => $session_flags, |
961
|
|
|
// we need the install-id to differ between serveral installs shareing one tmp-dir |
962
|
|
|
'session_install_id' => $GLOBALS['egw_info']['server']['install_id'] |
963
|
|
|
); |
964
|
|
|
} |
965
|
|
|
|
966
|
|
|
/** |
967
|
|
|
* name of access-log table |
968
|
|
|
*/ |
969
|
|
|
const ACCESS_LOG_TABLE = 'egw_access_log'; |
970
|
|
|
|
971
|
|
|
/** |
972
|
|
|
* Prefix used to log unsucessful login attempts in cache, if DB is unavailable |
973
|
|
|
*/ |
974
|
|
|
const FALSE_IP_CACHE_PREFIX = 'false_ip-'; |
975
|
|
|
const FALSE_ID_CACHE_PREFIX = 'false_id-'; |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* Write or update (for logout) the access_log |
979
|
|
|
* |
980
|
|
|
* We do NOT log anonymous sessions to not block website and also to cope with |
981
|
|
|
* high rate anon endpoints might be called creating a bottleneck in the egw_access_log table. |
982
|
|
|
* |
983
|
|
|
* @param string|int $sessionid nummeric or PHP session id or error-message for unsuccessful logins |
984
|
|
|
* @param string $login ='' account_lid (evtl. with domain) or '' for setting the logout-time |
985
|
|
|
* @param string $user_ip ='' ip to log |
986
|
|
|
* @param int $account_id =0 numerical account_id |
987
|
|
|
* @return int $sessionid primary key of egw_access_log for login, null otherwise |
988
|
|
|
*/ |
989
|
|
|
private function log_access($sessionid,$login='',$user_ip='',$account_id=0) |
990
|
|
|
{ |
991
|
|
|
// do not log anything for anonymous sessions |
992
|
|
|
if ($this->session_flags === 'A') |
993
|
|
|
{ |
994
|
|
|
return; |
995
|
|
|
} |
996
|
|
|
$now = time(); |
997
|
|
|
|
998
|
|
|
// if sessionid contains non-ascii chars (only happens for error-messages) |
999
|
|
|
// --> transliterate it to ascii, as session_php only allows ascii chars |
1000
|
|
|
if (preg_match('/[^\x20-\x7f]/', $sessionid)) |
1001
|
|
|
{ |
1002
|
|
|
$sessionid = Translation::to_ascii($sessionid); |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
|
|
if ($login) |
1006
|
|
|
{ |
1007
|
|
|
$GLOBALS['egw']->db->insert(self::ACCESS_LOG_TABLE,array( |
1008
|
|
|
'session_php' => $sessionid, |
1009
|
|
|
'loginid' => $login, |
1010
|
|
|
'ip' => $user_ip, |
1011
|
|
|
'li' => $now, |
1012
|
|
|
'account_id'=> $account_id, |
1013
|
|
|
'user_agent'=> $_SERVER['HTTP_USER_AGENT'], |
1014
|
|
|
'session_dla' => $now, |
1015
|
|
|
'session_action' => $this->update_dla(false), // dont update egw_access_log |
1016
|
|
|
),false,__LINE__,__FILE__); |
1017
|
|
|
|
1018
|
|
|
$_SESSION[self::EGW_SESSION_VAR]['session_logged_dla'] = $now; |
1019
|
|
|
|
1020
|
|
|
$ret = $GLOBALS['egw']->db->get_last_insert_id(self::ACCESS_LOG_TABLE,'sessionid'); |
1021
|
|
|
|
1022
|
|
|
// if we can not store failed login attempts in database, store it in cache |
1023
|
|
|
if (!$ret && !$account_id) |
1024
|
|
|
{ |
1025
|
|
|
Cache::setInstance(__CLASS__, self::FALSE_IP_CACHE_PREFIX.$user_ip, |
1026
|
|
|
1+Cache::getInstance(__CLASS__, self::FALSE_IP_CACHE_PREFIX.$user_ip), |
1027
|
|
|
$GLOBALS['egw_info']['server']['block_time'] * 60); |
1028
|
|
|
|
1029
|
|
|
Cache::setInstance(__CLASS__, self::FALSE_ID_CACHE_PREFIX.$login, |
1030
|
|
|
1+Cache::getInstance(__CLASS__, self::FALSE_ID_CACHE_PREFIX.$login), |
1031
|
|
|
$GLOBALS['egw_info']['server']['block_time'] * 60); |
1032
|
|
|
} |
1033
|
|
|
} |
1034
|
|
|
else |
1035
|
|
|
{ |
1036
|
|
|
if (!is_numeric($sessionid) && $sessionid == $this->sessionid && $this->sessionid_access_log) |
1037
|
|
|
{ |
1038
|
|
|
$sessionid = $this->sessionid_access_log; |
1039
|
|
|
} |
1040
|
|
|
$GLOBALS['egw']->db->update(self::ACCESS_LOG_TABLE,array( |
1041
|
|
|
'lo' => $now |
1042
|
|
|
),is_numeric($sessionid) ? array( |
1043
|
|
|
'sessionid' => $sessionid, |
1044
|
|
|
) : array( |
1045
|
|
|
'session_php' => $sessionid, |
1046
|
|
|
),__LINE__,__FILE__); |
1047
|
|
|
|
1048
|
|
|
// run maintenance only on logout, to not delay login |
1049
|
|
|
if ($GLOBALS['egw_info']['server']['max_access_log_age']) |
1050
|
|
|
{ |
1051
|
|
|
$max_age = $now - $GLOBALS['egw_info']['server']['max_access_log_age'] * 24 * 60 * 60; |
1052
|
|
|
|
1053
|
|
|
$GLOBALS['egw']->db->delete(self::ACCESS_LOG_TABLE,"li < $max_age",__LINE__,__FILE__); |
1054
|
|
|
} |
1055
|
|
|
} |
1056
|
|
|
//error_log(__METHOD__."('$sessionid', '$login', '$user_ip', $account_id) returning ".array2string($ret)); |
1057
|
|
|
return $ret; |
1058
|
|
|
} |
1059
|
|
|
|
1060
|
|
|
/** |
1061
|
|
|
* Protect against brute force attacks, block login if too many unsuccessful login attmepts |
1062
|
|
|
* |
1063
|
|
|
* @param string $login account_lid (evtl. with domain) |
1064
|
|
|
* @param string $ip ip of the user |
1065
|
|
|
* @returns bool login blocked? |
1066
|
|
|
*/ |
1067
|
|
|
private function login_blocked($login,$ip) |
1068
|
|
|
{ |
1069
|
|
|
$block_time = time() - $GLOBALS['egw_info']['server']['block_time'] * 60; |
1070
|
|
|
|
1071
|
|
|
$false_id = $false_ip = 0; |
1072
|
|
|
foreach($GLOBALS['egw']->db->union(array( |
1073
|
|
|
array( |
1074
|
|
|
'table' => self::ACCESS_LOG_TABLE, |
1075
|
|
|
'cols' => "'false_ip' AS name,COUNT(*) AS num", |
1076
|
|
|
'where' => array( |
1077
|
|
|
'account_id' => 0, |
1078
|
|
|
'ip' => $ip, |
1079
|
|
|
"li > $block_time", |
1080
|
|
|
), |
1081
|
|
|
), |
1082
|
|
|
array( |
1083
|
|
|
'table' => self::ACCESS_LOG_TABLE, |
1084
|
|
|
'cols' => "'false_id' AS name,COUNT(*) AS num", |
1085
|
|
|
'where' => array( |
1086
|
|
|
'account_id' => 0, |
1087
|
|
|
'loginid' => $login, |
1088
|
|
|
"li > $block_time", |
1089
|
|
|
), |
1090
|
|
|
), |
1091
|
|
|
array( |
1092
|
|
|
'table' => self::ACCESS_LOG_TABLE, |
1093
|
|
|
'cols' => "'false_id' AS name,COUNT(*) AS num", |
1094
|
|
|
'where' => array( |
1095
|
|
|
'account_id' => 0, |
1096
|
|
|
'loginid LIKE '.$GLOBALS['egw']->db->quote($login.'@%'), |
1097
|
|
|
"li > $block_time", |
1098
|
|
|
) |
1099
|
|
|
), |
1100
|
|
|
), __LINE__, __FILE__) as $row) |
1101
|
|
|
{ |
1102
|
|
|
${$row['name']} += $row['num']; |
1103
|
|
|
} |
1104
|
|
|
|
1105
|
|
|
// check cache too, in case DB is readonly |
1106
|
|
|
$false_ip += Cache::getInstance(__CLASS__, self::FALSE_IP_CACHE_PREFIX.$ip); |
1107
|
|
|
$false_id += Cache::getInstance(__CLASS__, self::FALSE_ID_CACHE_PREFIX.$login); |
1108
|
|
|
|
1109
|
|
|
// if IP matches one in the (comma-separated) whitelist |
1110
|
|
|
// --> check with whitelists optional number (none means never block) |
1111
|
|
|
$matches = null; |
1112
|
|
|
if (!empty($GLOBALS['egw_info']['server']['unsuccessful_ip_whitelist']) && |
1113
|
|
|
preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?/', |
1114
|
|
|
$GLOBALS['egw_info']['server']['unsuccessful_ip_whitelist'], $matches) && |
1115
|
|
|
($key=array_search($ip, $matches[1])) !== false) |
1116
|
|
|
{ |
1117
|
|
|
$blocked = !empty($matches[3][$key]) && $false_ip > $matches[3][$key]; |
1118
|
|
|
} |
1119
|
|
|
else // else check with general number |
1120
|
|
|
{ |
1121
|
|
|
$blocked = $false_ip > $GLOBALS['egw_info']['server']['num_unsuccessful_ip']; |
1122
|
|
|
} |
1123
|
|
|
if (!$blocked) |
1124
|
|
|
{ |
1125
|
|
|
$blocked = $false_id > $GLOBALS['egw_info']['server']['num_unsuccessful_id']; |
1126
|
|
|
} |
1127
|
|
|
//error_log(__METHOD__."('$login', '$ip') false_ip=$false_ip, false_id=$false_id --> blocked=".array2string($blocked)); |
1128
|
|
|
|
1129
|
|
|
if ($blocked && $GLOBALS['egw_info']['server']['admin_mails'] && |
1130
|
|
|
$GLOBALS['egw_info']['server']['login_blocked_mail_time'] < time()-5*60) // max. one mail every 5mins |
1131
|
|
|
{ |
1132
|
|
|
try { |
1133
|
|
|
$mailer = new Mailer(); |
1134
|
|
|
// notify admin(s) via email |
1135
|
|
|
$mailer->setFrom('eGroupWare@'.$GLOBALS['egw_info']['server']['mail_suffix']); |
1136
|
|
|
$mailer->addHeader('Subject', lang("eGroupWare: login blocked for user '%1', IP %2",$login,$ip)); |
|
|
|
|
1137
|
|
|
$mailer->setBody(lang("Too many unsucessful attempts to login: %1 for the user '%2', %3 for the IP %4",$false_id,$login,$false_ip,$ip)); |
1138
|
|
|
foreach(preg_split('/,\s*/',$GLOBALS['egw_info']['server']['admin_mails']) as $mail) |
1139
|
|
|
{ |
1140
|
|
|
$mailer->addAddress($mail); |
1141
|
|
|
} |
1142
|
|
|
$mailer->send(); |
1143
|
|
|
} |
1144
|
|
|
catch(\Exception $e) { |
1145
|
|
|
// ignore exception, but log it, to block the account and give a correct error-message to user |
1146
|
|
|
error_log(__METHOD__."('$login', '$ip') ".$e->getMessage()); |
1147
|
|
|
} |
1148
|
|
|
// save time of mail, to not send to many mails |
1149
|
|
|
$config = new Config('phpgwapi'); |
1150
|
|
|
$config->read_repository(); |
1151
|
|
|
$config->value('login_blocked_mail_time',time()); |
1152
|
|
|
$config->save_repository(); |
1153
|
|
|
} |
1154
|
|
|
return $blocked; |
1155
|
|
|
} |
1156
|
|
|
|
1157
|
|
|
/** |
1158
|
|
|
* Basename of scripts for which we create a pseudo session-id based on user-credentials |
1159
|
|
|
* |
1160
|
|
|
* @var array |
1161
|
|
|
*/ |
1162
|
|
|
static $pseudo_session_scripts = array( |
1163
|
|
|
'webdav.php', 'groupdav.php', 'remote.php', 'share.php' |
1164
|
|
|
); |
1165
|
|
|
|
1166
|
|
|
/** |
1167
|
|
|
* Get the sessionid from Cookie, Get-Parameter or basic auth |
1168
|
|
|
* |
1169
|
|
|
* @param boolean $only_basic_auth =false return only a basic auth pseudo sessionid, default no |
1170
|
|
|
* @return string|null (pseudo-)session-id use or NULL if no Cookie or Basic-Auth credentials |
1171
|
|
|
*/ |
1172
|
|
|
static function get_sessionid($only_basic_auth=false) |
1173
|
|
|
{ |
1174
|
|
|
// for WebDAV and GroupDAV we use a pseudo sessionid created from md5(user:passwd) |
1175
|
|
|
// --> allows this stateless protocolls which use basic auth to use sessions! |
1176
|
|
|
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) && |
1177
|
|
|
(in_array(basename($_SERVER['SCRIPT_NAME']), self::$pseudo_session_scripts) || |
1178
|
|
|
$_SERVER['SCRIPT_NAME'] === '/Microsoft-Server-ActiveSync')) |
1179
|
|
|
{ |
1180
|
|
|
// we generate a pseudo-sessionid from the basic auth credentials |
1181
|
|
|
$sessionid = md5($_SERVER['PHP_AUTH_USER'].':'.$_SERVER['PHP_AUTH_PW'].':'.$_SERVER['HTTP_HOST'].':'. |
1182
|
|
|
EGW_SERVER_ROOT.':'.self::getuser_ip().':'.filemtime(EGW_SERVER_ROOT.'/api/setup/setup.inc.php'). |
1183
|
|
|
// for ActiveSync we add the DeviceID |
1184
|
|
|
(isset($_GET['DeviceId']) && $_SERVER['SCRIPT_NAME'] === '/Microsoft-Server-ActiveSync' ? ':'.$_GET['DeviceId'] : ''). |
1185
|
|
|
':'.$_SERVER['HTTP_USER_AGENT']); |
1186
|
|
|
//error_log(__METHOD__."($only_basic_auth) HTTP_HOST=$_SERVER[HTTP_HOST], PHP_AUTH_USER=$_SERVER[PHP_AUTH_USER], DeviceId=$_GET[DeviceId]: sessionid=$sessionid"); |
1187
|
|
|
} |
1188
|
|
|
// same for digest auth |
1189
|
|
|
elseif (isset($_SERVER['PHP_AUTH_DIGEST']) && |
1190
|
|
|
in_array(basename($_SERVER['SCRIPT_NAME']), self::$pseudo_session_scripts)) |
1191
|
|
|
{ |
1192
|
|
|
// we generate a pseudo-sessionid from the digest username, realm and nounce |
1193
|
|
|
// can't use full $_SERVER['PHP_AUTH_DIGEST'], as it changes (contains eg. the url) |
1194
|
|
|
$data = Header\Authenticate::parse_digest($_SERVER['PHP_AUTH_DIGEST']); |
1195
|
|
|
$sessionid = md5($data['username'].':'.$data['realm'].':'.$data['nonce'].':'.$_SERVER['HTTP_HOST']. |
1196
|
|
|
EGW_SERVER_ROOT.':'.self::getuser_ip().':'.filemtime(EGW_SERVER_ROOT.'/api/setup/setup.inc.php'). |
1197
|
|
|
':'.$_SERVER['HTTP_USER_AGENT']); |
1198
|
|
|
} |
1199
|
|
|
elseif(!$only_basic_auth && isset($_REQUEST[self::EGW_SESSION_NAME])) |
1200
|
|
|
{ |
1201
|
|
|
$sessionid = $_REQUEST[self::EGW_SESSION_NAME]; |
1202
|
|
|
} |
1203
|
|
|
elseif(!$only_basic_auth && isset($_COOKIE[self::EGW_SESSION_NAME])) |
1204
|
|
|
{ |
1205
|
|
|
$sessionid = $_COOKIE[self::EGW_SESSION_NAME]; |
1206
|
|
|
} |
1207
|
|
|
else |
1208
|
|
|
{ |
1209
|
|
|
$sessionid = null; |
1210
|
|
|
} |
1211
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() _SERVER[REQUEST_URI]='$_SERVER[REQUEST_URI]' returning ".print_r($sessionid,true)); |
1212
|
|
|
return $sessionid; |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
/** |
1216
|
|
|
* Get request or cookie variable with higher precedence to $_REQUEST then $_COOKIE |
1217
|
|
|
* |
1218
|
|
|
* In php < 5.3 that's identical to $_REQUEST[$name], but php5.3+ does no longer register cookied in $_REQUEST by default |
1219
|
|
|
* |
1220
|
|
|
* As a workaround for a bug in Safari Version 3.2.1 (5525.27.1), where cookie first letter get's upcased, we check that too. |
1221
|
|
|
* |
1222
|
|
|
* @param string $name eg. 'kp3' or domain |
1223
|
|
|
* @return mixed null if it's neither set in $_REQUEST or $_COOKIE |
1224
|
|
|
*/ |
1225
|
|
|
static function get_request($name) |
1226
|
|
|
{ |
1227
|
|
|
return isset($_REQUEST[$name]) ? $_REQUEST[$name] : |
1228
|
|
|
(isset($_COOKIE[$name]) ? $_COOKIE[$name] : |
1229
|
|
|
(isset($_COOKIE[$name=ucfirst($name)]) ? $_COOKIE[$name] : null)); |
1230
|
|
|
} |
1231
|
|
|
|
1232
|
|
|
/** |
1233
|
|
|
* Check to see if a session is still current and valid |
1234
|
|
|
* |
1235
|
|
|
* @param string $sessionid session id to be verfied |
1236
|
|
|
* @param string $kp3 ?? to be verified |
1237
|
|
|
* @return bool is the session valid? |
1238
|
|
|
*/ |
1239
|
|
|
function verify($sessionid=null,$kp3=null) |
1240
|
|
|
{ |
1241
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$sessionid','$kp3') ".function_backtrace()); |
1242
|
|
|
|
1243
|
|
|
$fill_egw_info_and_repositories = !$GLOBALS['egw_info']['flags']['restored_from_session']; |
1244
|
|
|
|
1245
|
|
|
if(!$sessionid) |
1246
|
|
|
{ |
1247
|
|
|
$sessionid = self::get_sessionid(); |
1248
|
|
|
$kp3 = self::get_request('kp3'); |
1249
|
|
|
} |
1250
|
|
|
|
1251
|
|
|
$this->sessionid = $sessionid; |
1252
|
|
|
$this->kp3 = $kp3; |
1253
|
|
|
|
1254
|
|
|
|
1255
|
|
|
if (!$this->sessionid) |
1256
|
|
|
{ |
1257
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$sessionid') get_sessionid()='".self::get_sessionid()."' No session ID"); |
1258
|
|
|
return false; |
1259
|
|
|
} |
1260
|
|
|
|
1261
|
|
|
switch (session_status()) |
1262
|
|
|
{ |
1263
|
|
|
case PHP_SESSION_DISABLED: |
1264
|
|
|
throw new ErrorException('EGroupware requires the PHP session extension!'); |
|
|
|
|
1265
|
|
|
case PHP_SESSION_NONE: |
1266
|
|
|
session_name(self::EGW_SESSION_NAME); |
1267
|
|
|
session_id($this->sessionid); |
1268
|
|
|
self::cache_control(); |
1269
|
|
|
session_start(); |
1270
|
|
|
break; |
1271
|
|
|
case PHP_SESSION_ACTIVE: |
1272
|
|
|
// session already started eg. by managementserver_client |
1273
|
|
|
} |
1274
|
|
|
|
1275
|
|
|
// check if we have a eGroupware session --> return false if not (but dont destroy it!) |
1276
|
|
|
if (is_null($_SESSION) || !isset($_SESSION[self::EGW_SESSION_VAR])) |
1277
|
|
|
{ |
1278
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$sessionid') session does NOT exist!"); |
1279
|
|
|
return false; |
1280
|
|
|
} |
1281
|
|
|
$session =& $_SESSION[self::EGW_SESSION_VAR]; |
1282
|
|
|
|
1283
|
|
|
if ($session['session_dla'] <= time() - $GLOBALS['egw_info']['server']['sessions_timeout']) |
1284
|
|
|
{ |
1285
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$sessionid') session timed out!"); |
1286
|
|
|
$this->destroy($sessionid,$kp3); |
1287
|
|
|
return false; |
1288
|
|
|
} |
1289
|
|
|
|
1290
|
|
|
$this->session_flags = $session['session_flags']; |
1291
|
|
|
|
1292
|
|
|
$this->split_login_domain($session['session_lid'],$this->account_lid,$this->account_domain); |
1293
|
|
|
|
1294
|
|
|
// This is to ensure that we authenticate to the correct domain (might not be default) |
1295
|
|
|
if($GLOBALS['egw_info']['user']['domain'] && $this->account_domain != $GLOBALS['egw_info']['user']['domain']) |
1296
|
|
|
{ |
1297
|
|
|
return false; // session not verified, domain changed |
1298
|
|
|
} |
1299
|
|
|
$GLOBALS['egw_info']['user']['kp3'] = $this->kp3; |
1300
|
|
|
|
1301
|
|
|
// allow xajax / notifications to not update the dla, so sessions can time out again |
1302
|
|
|
if (!isset($GLOBALS['egw_info']['flags']['no_dla_update']) || !$GLOBALS['egw_info']['flags']['no_dla_update']) |
1303
|
|
|
{ |
1304
|
|
|
$this->update_dla(true); |
1305
|
|
|
} |
1306
|
|
|
elseif ($GLOBALS['egw_info']['flags']['currentapp'] == 'notifications') |
1307
|
|
|
{ |
1308
|
|
|
$this->update_notification_heartbeat(); |
1309
|
|
|
} |
1310
|
|
|
$this->account_id = $GLOBALS['egw']->accounts->name2id($this->account_lid,'account_lid','u'); |
1311
|
|
|
if (!$this->account_id) |
1312
|
|
|
{ |
1313
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) !accounts::name2id('$this->account_lid')"); |
1314
|
|
|
return false; |
1315
|
|
|
} |
1316
|
|
|
|
1317
|
|
|
$GLOBALS['egw_info']['user']['account_id'] = $this->account_id; |
1318
|
|
|
|
1319
|
|
|
if ($fill_egw_info_and_repositories) |
1320
|
|
|
{ |
1321
|
|
|
$GLOBALS['egw_info']['user'] = $this->read_repositories(); |
1322
|
|
|
} |
1323
|
|
|
else |
1324
|
|
|
{ |
1325
|
|
|
// restore apps to $GLOBALS['egw_info']['apps'] |
1326
|
|
|
$GLOBALS['egw']->applications->read_installed_apps(); |
1327
|
|
|
|
1328
|
|
|
// session only stores app-names, restore apps from egw_info[apps] |
1329
|
|
|
if (isset($GLOBALS['egw_info']['user']['apps'][0])) |
1330
|
|
|
{ |
1331
|
|
|
$GLOBALS['egw_info']['user']['apps'] = array_intersect_key($GLOBALS['egw_info']['apps'], array_flip($GLOBALS['egw_info']['user']['apps'])); |
1332
|
|
|
} |
1333
|
|
|
|
1334
|
|
|
// set prefs, they are no longer stored in session |
1335
|
|
|
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository(); |
1336
|
|
|
} |
1337
|
|
|
|
1338
|
|
|
if ($GLOBALS['egw']->accounts->is_expired($GLOBALS['egw_info']['user'])) |
1339
|
|
|
{ |
1340
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) accounts is expired"); |
1341
|
|
|
return false; |
1342
|
|
|
} |
1343
|
|
|
$this->passwd = base64_decode(Cache::getSession('phpgwapi', 'password')); |
1344
|
|
|
if ($fill_egw_info_and_repositories) |
1345
|
|
|
{ |
1346
|
|
|
$GLOBALS['egw_info']['user']['session_ip'] = $session['session_ip']; |
1347
|
|
|
$GLOBALS['egw_info']['user']['passwd'] = $this->passwd; |
1348
|
|
|
} |
1349
|
|
|
if ($this->account_domain != $GLOBALS['egw_info']['user']['domain']) |
1350
|
|
|
{ |
1351
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) wrong domain"); |
1352
|
|
|
return false; |
1353
|
|
|
} |
1354
|
|
|
|
1355
|
|
|
if ($GLOBALS['egw_info']['server']['sessions_checkip']) |
1356
|
|
|
{ |
1357
|
|
|
if (strtoupper(substr(PHP_OS,0,3)) != 'WIN' && (!$GLOBALS['egw_info']['user']['session_ip'] || |
1358
|
|
|
$GLOBALS['egw_info']['user']['session_ip'] != $this->getuser_ip())) |
1359
|
|
|
{ |
1360
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) wrong IP"); |
1361
|
|
|
return false; |
1362
|
|
|
} |
1363
|
|
|
} |
1364
|
|
|
|
1365
|
|
|
if ($fill_egw_info_and_repositories) |
1366
|
|
|
{ |
1367
|
|
|
$GLOBALS['egw']->acl->__construct($this->account_id); |
1368
|
|
|
$GLOBALS['egw']->preferences->__construct($this->account_id); |
1369
|
|
|
$GLOBALS['egw']->applications->__construct($this->account_id); |
1370
|
|
|
} |
1371
|
|
|
if (!$this->account_lid) |
1372
|
|
|
{ |
1373
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("*** Session::verify($sessionid) !account_lid"); |
1374
|
|
|
return false; |
1375
|
|
|
} |
1376
|
|
|
|
1377
|
|
|
// query accesslog-id, if not set in session (session is made persistent after login!) |
1378
|
|
|
if (!$this->sessionid_access_log && $this->session_flags != 'A') |
1379
|
|
|
{ |
1380
|
|
|
$this->sessionid_access_log = $GLOBALS['egw']->db->select(self::ACCESS_LOG_TABLE,'sessionid',array( |
1381
|
|
|
'session_php' => $this->sessionid, |
1382
|
|
|
),__LINE__,__FILE__)->fetchColumn(); |
1383
|
|
|
//error_log(__METHOD__."() sessionid=$this->sessionid --> sessionid_access_log=$this->sessionid_access_log"); |
1384
|
|
|
} |
1385
|
|
|
|
1386
|
|
|
// check if we use cookies for the session, but no cookie set |
1387
|
|
|
// happens eg. in sitemgr (when redirecting to a different domain) or with new java notification app |
1388
|
|
|
if ($GLOBALS['egw_info']['server']['usecookies'] && isset($_REQUEST[self::EGW_SESSION_NAME]) && |
1389
|
|
|
$_REQUEST[self::EGW_SESSION_NAME] === $this->sessionid && |
1390
|
|
|
(!isset($_COOKIE[self::EGW_SESSION_NAME]) || $_COOKIE[self::EGW_SESSION_NAME] !== $_REQUEST[self::EGW_SESSION_NAME])) |
1391
|
|
|
{ |
1392
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("--> Session::verify($sessionid) SUCCESS, but NO required cookies set --> setting them now"); |
1393
|
|
|
self::egw_setcookie(self::EGW_SESSION_NAME,$this->sessionid); |
1394
|
|
|
self::egw_setcookie('kp3',$this->kp3); |
1395
|
|
|
self::egw_setcookie('domain',$this->account_domain); |
1396
|
|
|
} |
1397
|
|
|
|
1398
|
|
|
if (self::ERROR_LOG_DEBUG) error_log("--> Session::verify($sessionid) SUCCESS"); |
1399
|
|
|
|
1400
|
|
|
return true; |
1401
|
|
|
} |
1402
|
|
|
|
1403
|
|
|
/** |
1404
|
|
|
* Terminate a session |
1405
|
|
|
* |
1406
|
|
|
* @param int|string $sessionid nummeric or php session id of session to be terminated |
1407
|
|
|
* @param string $kp3 |
1408
|
|
|
* @return boolean true on success, false on error |
1409
|
|
|
*/ |
1410
|
|
|
function destroy($sessionid, $kp3='') |
1411
|
|
|
{ |
1412
|
|
|
if (!$sessionid && $kp3) |
1413
|
|
|
{ |
1414
|
|
|
return false; |
1415
|
|
|
} |
1416
|
|
|
$this->log_access($sessionid); // log logout-time |
1417
|
|
|
|
1418
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($sessionid,$kp3)"); |
1419
|
|
|
|
1420
|
|
|
if (is_numeric($sessionid)) // do we have a access-log-id --> get PHP session id |
1421
|
|
|
{ |
1422
|
|
|
$sessionid = $GLOBALS['egw']->db->select(self::ACCESS_LOG_TABLE,'session_php',array( |
1423
|
|
|
'sessionid' => $sessionid, |
1424
|
|
|
),__LINE__,__FILE__)->fetchColumn(); |
1425
|
|
|
} |
1426
|
|
|
|
1427
|
|
|
Hooks::process(array( |
1428
|
|
|
'location' => 'session_destroyed', |
1429
|
|
|
'sessionid' => $sessionid, |
1430
|
|
|
),'',true); // true = run hooks from all apps, not just the ones the current user has perms to run |
1431
|
|
|
|
1432
|
|
|
// Only do the following, if where working with the current user |
1433
|
|
|
if (!$GLOBALS['egw_info']['user']['sessionid'] || $sessionid == $GLOBALS['egw_info']['user']['sessionid']) |
1434
|
|
|
{ |
1435
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__." ********* about to call session_destroy!"); |
1436
|
|
|
session_unset(); |
1437
|
|
|
@session_destroy(); |
|
|
|
|
1438
|
|
|
// we need to (re-)load the eGW session-handler, as session_destroy unloads custom session-handlers |
1439
|
|
|
if (function_exists('init_session_handler')) |
1440
|
|
|
{ |
1441
|
|
|
init_session_handler(); |
1442
|
|
|
} |
1443
|
|
|
|
1444
|
|
|
if ($GLOBALS['egw_info']['server']['usecookies']) |
1445
|
|
|
{ |
1446
|
|
|
self::egw_setcookie(session_name()); |
1447
|
|
|
} |
1448
|
|
|
} |
1449
|
|
|
else |
1450
|
|
|
{ |
1451
|
|
|
$this->commit_session(); // close our own session |
1452
|
|
|
|
1453
|
|
|
session_id($sessionid); |
1454
|
|
|
if (session_start()) |
1455
|
|
|
{ |
1456
|
|
|
session_destroy(); |
1457
|
|
|
} |
1458
|
|
|
} |
1459
|
|
|
return true; |
1460
|
|
|
} |
1461
|
|
|
|
1462
|
|
|
/** |
1463
|
|
|
* Generate a url which supports url or cookies based sessions |
1464
|
|
|
* |
1465
|
|
|
* Please note, the values of the query get url encoded! |
1466
|
|
|
* |
1467
|
|
|
* @param string $url a url relative to the egroupware install root, it can contain a query too |
1468
|
|
|
* @param array|string $extravars query string arguements as string or array (prefered) |
1469
|
|
|
* if string is used ambersands in vars have to be already urlencoded as '%26', function ensures they get NOT double encoded |
1470
|
|
|
* @return string generated url |
1471
|
|
|
*/ |
1472
|
|
|
public static function link($url, $extravars = '') |
1473
|
|
|
{ |
1474
|
|
|
//error_log(_METHOD__."(url='$url',extravars='".array2string($extravars)."')"); |
1475
|
|
|
|
1476
|
|
|
if ($url[0] != '/') |
1477
|
|
|
{ |
1478
|
|
|
$app = $GLOBALS['egw_info']['flags']['currentapp']; |
1479
|
|
|
if ($app != 'login' && $app != 'logout') |
1480
|
|
|
{ |
1481
|
|
|
$url = $app.'/'.$url; |
1482
|
|
|
} |
1483
|
|
|
} |
1484
|
|
|
|
1485
|
|
|
// append the url to the webserver url, but avoid more then one slash between the parts of the url |
1486
|
|
|
$webserver_url = $GLOBALS['egw_info']['server']['webserver_url']; |
1487
|
|
|
// patch inspired by vladimir kolobkov -> we should not try to match the webserver url against the url without '/' as delimiter, |
1488
|
|
|
// as $webserver_url may be part of $url (as /egw is part of phpgwapi/js/egw_instant_load.html) |
1489
|
|
|
if (($url[0] != '/' || $webserver_url != '/') && (!$webserver_url || strpos($url, $webserver_url.'/') === false)) |
1490
|
|
|
{ |
1491
|
|
|
if($url[0] != '/' && substr($webserver_url,-1) != '/') |
1492
|
|
|
{ |
1493
|
|
|
$url = $webserver_url .'/'. $url; |
1494
|
|
|
} |
1495
|
|
|
else |
1496
|
|
|
{ |
1497
|
|
|
$url = $webserver_url . $url; |
1498
|
|
|
} |
1499
|
|
|
} |
1500
|
|
|
|
1501
|
|
|
if(isset($GLOBALS['egw_info']['server']['enforce_ssl']) && $GLOBALS['egw_info']['server']['enforce_ssl']) |
1502
|
|
|
{ |
1503
|
|
|
if(substr($url ,0,4) != 'http') |
1504
|
|
|
{ |
1505
|
|
|
$url = 'https://'.$_SERVER['HTTP_HOST'].$url; |
1506
|
|
|
} |
1507
|
|
|
else |
1508
|
|
|
{ |
1509
|
|
|
$url = str_replace ( 'http:', 'https:', $url); |
1510
|
|
|
} |
1511
|
|
|
} |
1512
|
|
|
$vars = array(); |
1513
|
|
|
// add session params if not using cookies |
1514
|
|
|
if (!$GLOBALS['egw_info']['server']['usecookies']) |
1515
|
|
|
{ |
1516
|
|
|
$vars[self::EGW_SESSION_NAME] = $GLOBALS['egw']->session->sessionid; |
1517
|
|
|
$vars['kp3'] = $GLOBALS['egw']->session->kp3; |
1518
|
|
|
$vars['domain'] = $GLOBALS['egw']->session->account_domain; |
1519
|
|
|
} |
1520
|
|
|
|
1521
|
|
|
// check if the url already contains a query and ensure that vars is an array and all strings are in extravars |
1522
|
|
|
list($ret_url,$othervars) = explode('?', $url, 2); |
1523
|
|
|
if ($extravars && is_array($extravars)) |
1524
|
|
|
{ |
1525
|
|
|
$vars += $extravars; |
1526
|
|
|
$extravars = $othervars; |
1527
|
|
|
} |
1528
|
|
|
else |
1529
|
|
|
{ |
1530
|
|
|
if ($othervars) $extravars .= ($extravars?'&':'').$othervars; |
1531
|
|
|
} |
1532
|
|
|
|
1533
|
|
|
// parse extravars string into the vars array |
1534
|
|
|
if ($extravars) |
1535
|
|
|
{ |
1536
|
|
|
foreach(explode('&',$extravars) as $expr) |
1537
|
|
|
{ |
1538
|
|
|
list($var,$val) = explode('=', $expr,2); |
1539
|
|
|
if (strpos($val,'%26') != false) $val = str_replace('%26','&',$val); // make sure to not double encode & |
|
|
|
|
1540
|
|
|
if (substr($var,-2) == '[]') |
1541
|
|
|
{ |
1542
|
|
|
$vars[substr($var,0,-2)][] = $val; |
1543
|
|
|
} |
1544
|
|
|
else |
1545
|
|
|
{ |
1546
|
|
|
$vars[$var] = $val; |
1547
|
|
|
} |
1548
|
|
|
} |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
// if there are vars, we add them urlencoded to the url |
1552
|
|
|
if (count($vars)) |
1553
|
|
|
{ |
1554
|
|
|
$query = array(); |
1555
|
|
|
foreach($vars as $key => $value) |
1556
|
|
|
{ |
1557
|
|
|
if (is_array($value)) |
1558
|
|
|
{ |
1559
|
|
|
foreach($value as $val) |
1560
|
|
|
{ |
1561
|
|
|
$query[] = $key.'[]='.urlencode($val); |
1562
|
|
|
} |
1563
|
|
|
} |
1564
|
|
|
else |
1565
|
|
|
{ |
1566
|
|
|
$query[] = $key.'='.urlencode($value); |
1567
|
|
|
} |
1568
|
|
|
} |
1569
|
|
|
$ret_url .= '?' . implode('&',$query); |
1570
|
|
|
} |
1571
|
|
|
return $ret_url; |
1572
|
|
|
} |
1573
|
|
|
|
1574
|
|
|
/** |
1575
|
|
|
* Regexp to validate IPv4 and IPv6 |
1576
|
|
|
*/ |
1577
|
|
|
const IP_REGEXP = '/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$/iD'; |
1578
|
|
|
|
1579
|
|
|
/** |
1580
|
|
|
* Get the ip address of current users |
1581
|
|
|
* |
1582
|
|
|
* We remove further private IPs (from proxys) as they invalidate user |
1583
|
|
|
* sessions, when they change because of multiple proxys. |
1584
|
|
|
* |
1585
|
|
|
* @return string ip address |
1586
|
|
|
*/ |
1587
|
|
|
public static function getuser_ip() |
1588
|
|
|
{ |
1589
|
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) |
1590
|
|
|
{ |
1591
|
|
|
$forwarded_for = preg_replace('/, *10\..*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); |
1592
|
|
|
if (preg_match(self::IP_REGEXP, $forwarded_for)) |
1593
|
|
|
{ |
1594
|
|
|
return $forwarded_for; |
1595
|
|
|
} |
1596
|
|
|
} |
1597
|
|
|
return $_SERVER['REMOTE_ADDR']; |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
/** |
1601
|
|
|
* domain for cookies |
1602
|
|
|
* |
1603
|
|
|
* @var string |
1604
|
|
|
*/ |
1605
|
|
|
private static $cookie_domain = ''; |
1606
|
|
|
|
1607
|
|
|
/** |
1608
|
|
|
* path for cookies |
1609
|
|
|
* |
1610
|
|
|
* @var string |
1611
|
|
|
*/ |
1612
|
|
|
private static $cookie_path = '/'; |
1613
|
|
|
|
1614
|
|
|
/** |
1615
|
|
|
* iOS web-apps will loose cookie if set with a livetime of 0 / session-cookie |
1616
|
|
|
* |
1617
|
|
|
* Therefore we set a fixed lifetime of 24h from session-start instead. |
1618
|
|
|
* Server-side session will timeout earliert anyway, if there's no activity. |
1619
|
|
|
*/ |
1620
|
|
|
const IOS_SESSION_COOKIE_LIFETIME = 86400; |
1621
|
|
|
|
1622
|
|
|
/** |
1623
|
|
|
* Set a cookie with eGW's cookie-domain and -path settings |
1624
|
|
|
* |
1625
|
|
|
* @param string $cookiename name of cookie to be set |
1626
|
|
|
* @param string $cookievalue ='' value to be used, if unset cookie is cleared (optional) |
1627
|
|
|
* @param int $cookietime =0 when cookie should expire, 0 for session only (optional) |
1628
|
|
|
* @param string $cookiepath =null optional path (eg. '/') if the eGW install-dir should not be used |
1629
|
|
|
*/ |
1630
|
|
|
public static function egw_setcookie($cookiename,$cookievalue='',$cookietime=0,$cookiepath=null) |
1631
|
|
|
{ |
1632
|
|
|
if (empty(self::$cookie_domain) || empty(self::$cookie_path)) |
1633
|
|
|
{ |
1634
|
|
|
self::set_cookiedomain(); |
1635
|
|
|
} |
1636
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($cookiename,$cookievalue,$cookietime,$cookiepath,".self::$cookie_domain.")"); |
1637
|
|
|
|
1638
|
|
|
// if we are installed in iOS as web-app, we must not set a cookietime==0 (session-cookie), |
1639
|
|
|
// as every change between apps will cause the cookie to get lost |
1640
|
|
|
static $is_iOS = null; |
1641
|
|
|
if (!$cookietime && !isset($is_iOS)) $is_iOS = (bool)preg_match('/^(iPhone|iPad|iPod)/i', Header\UserAgent::mobile()); |
1642
|
|
|
|
1643
|
|
|
if(!headers_sent()) // gives only a warning, but can not send the cookie anyway |
1644
|
|
|
{ |
1645
|
|
|
setcookie($cookiename, $cookievalue, |
1646
|
|
|
!$cookietime && $is_iOS ? time()+self::IOS_SESSION_COOKIE_LIFETIME : $cookietime, |
1647
|
|
|
is_null($cookiepath) ? self::$cookie_path : $cookiepath,self::$cookie_domain, |
1648
|
|
|
// if called via HTTPS, only send cookie for https and only allow cookie access via HTTP (true) |
1649
|
|
|
empty($GLOBALS['egw_info']['server']['insecure_cookies']) && Header\Http::schema() === 'https', true); |
1650
|
|
|
} |
1651
|
|
|
} |
1652
|
|
|
|
1653
|
|
|
/** |
1654
|
|
|
* Set the domain and path used for cookies |
1655
|
|
|
*/ |
1656
|
|
|
private static function set_cookiedomain() |
1657
|
|
|
{ |
1658
|
|
|
if ($GLOBALS['egw_info']['server']['cookiedomain']) |
1659
|
|
|
{ |
1660
|
|
|
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com |
1661
|
|
|
self::$cookie_domain = $GLOBALS['egw_info']['server']['cookiedomain']; |
1662
|
|
|
} |
1663
|
|
|
else |
1664
|
|
|
{ |
1665
|
|
|
// Use HTTP_X_FORWARDED_HOST if set, which is the case behind a none-transparent proxy |
1666
|
|
|
self::$cookie_domain = Header\Http::host(); |
1667
|
|
|
} |
1668
|
|
|
// remove port from HTTP_HOST |
1669
|
|
|
$arr = null; |
1670
|
|
|
if (preg_match("/^(.*):(.*)$/",self::$cookie_domain,$arr)) |
1671
|
|
|
{ |
1672
|
|
|
self::$cookie_domain = $arr[1]; |
1673
|
|
|
} |
1674
|
|
|
if (count(explode('.',self::$cookie_domain)) <= 1) |
1675
|
|
|
{ |
1676
|
|
|
// setcookie dont likes domains without dots, leaving it empty, gets setcookie to fill the domain in |
1677
|
|
|
self::$cookie_domain = ''; |
1678
|
|
|
} |
1679
|
|
|
if (!$GLOBALS['egw_info']['server']['cookiepath'] || |
1680
|
|
|
!(self::$cookie_path = parse_url($GLOBALS['egw_info']['server']['webserver_url'],PHP_URL_PATH))) |
1681
|
|
|
{ |
1682
|
|
|
self::$cookie_path = '/'; |
1683
|
|
|
} |
1684
|
|
|
|
1685
|
|
|
session_set_cookie_params(0, self::$cookie_path, self::$cookie_domain, |
1686
|
|
|
// if called via HTTPS, only send cookie for https and only allow cookie access via HTTP (true) |
1687
|
|
|
empty($GLOBALS['egw_info']['server']['insecure_cookies']) && Header\Http::schema() === 'https', true); |
1688
|
|
|
} |
1689
|
|
|
|
1690
|
|
|
/** |
1691
|
|
|
* Search the instance matching the request |
1692
|
|
|
* |
1693
|
|
|
* @param string $login on login $_POST['login'], $_SERVER['PHP_AUTH_USER'] or $_SERVER['REMOTE_USER'] |
1694
|
|
|
* @param string $domain_requested usually self::get_request('domain') |
1695
|
|
|
* @param string &$default_domain usually $default_domain get's set eg. by sitemgr |
1696
|
|
|
* @param string|array $server_names usually array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) |
1697
|
|
|
* @param array $domains =null defaults to $GLOBALS['egw_domain'] from the header |
1698
|
|
|
* @return string $GLOBALS['egw_info']['user']['domain'] set with the domain/instance to use |
1699
|
|
|
*/ |
1700
|
|
|
public static function search_instance($login,$domain_requested,&$default_domain,$server_names,array $domains=null) |
1701
|
|
|
{ |
1702
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$login','$domain_requested',".array2string($default_domain).".".array2string($server_names).".".array2string($domains).")"); |
1703
|
|
|
|
1704
|
|
|
if (is_null($domains)) $domains = $GLOBALS['egw_domain']; |
1705
|
|
|
|
1706
|
|
|
if (!isset($default_domain) || !isset($domains[$default_domain])) // allow to overwrite the default domain |
1707
|
|
|
{ |
1708
|
|
|
foreach((array)$server_names as $server_name) |
1709
|
|
|
{ |
1710
|
|
|
list($server_name) = explode(':', $server_name); // remove port from HTTP_HOST |
1711
|
|
|
if(isset($domains[$server_name])) |
1712
|
|
|
{ |
1713
|
|
|
$default_domain = $server_name; |
1714
|
|
|
break; |
1715
|
|
|
} |
1716
|
|
|
else |
1717
|
|
|
{ |
1718
|
|
|
$parts = explode('.', $server_name); |
1719
|
|
|
array_shift($parts); |
1720
|
|
|
$domain_part = implode('.', $parts); |
1721
|
|
|
if(isset($domains[$domain_part])) |
1722
|
|
|
{ |
1723
|
|
|
$default_domain = $domain_part; |
1724
|
|
|
break; |
1725
|
|
|
} |
1726
|
|
|
else |
1727
|
|
|
{ |
1728
|
|
|
reset($domains); |
1729
|
|
|
$default_domain = key($domains); |
1730
|
|
|
} |
1731
|
|
|
unset($domain_part); |
1732
|
|
|
} |
1733
|
|
|
} |
1734
|
|
|
} |
1735
|
|
|
if (isset($login)) // on login |
1736
|
|
|
{ |
1737
|
|
|
if (strpos($login,'@') === false || count($domains) == 1) |
1738
|
|
|
{ |
1739
|
|
|
$login .= '@' . (isset($_POST['logindomain']) ? $_POST['logindomain'] : $default_domain); |
1740
|
|
|
} |
1741
|
|
|
$parts = explode('@',$login); |
1742
|
|
|
$domain = array_pop($parts); |
1743
|
|
|
$GLOBALS['login'] = $login; |
1744
|
|
|
} |
1745
|
|
|
else // on "normal" pageview |
1746
|
|
|
{ |
1747
|
|
|
$domain = $domain_requested; |
1748
|
|
|
} |
1749
|
|
|
if (!isset($domains[$domain])) |
1750
|
|
|
{ |
1751
|
|
|
$domain = $default_domain; |
1752
|
|
|
} |
1753
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() default_domain=".array2string($default_domain).', login='.array2string($login)." returning ".array2string($domain)); |
1754
|
|
|
|
1755
|
|
|
return $domain; |
1756
|
|
|
} |
1757
|
|
|
|
1758
|
|
|
/** |
1759
|
|
|
* Set action logged in access-log |
1760
|
|
|
* |
1761
|
|
|
* Non-ascii chars in $action get transliterate to ascii, as our session_action column allows only ascii. |
1762
|
|
|
* |
1763
|
|
|
* @param string $action |
1764
|
|
|
*/ |
1765
|
|
|
public function set_action($action) |
1766
|
|
|
{ |
1767
|
|
|
if (preg_match('/[^\x20-\x7f]/', $action)) |
1768
|
|
|
{ |
1769
|
|
|
$action = Translation::to_ascii($action); |
1770
|
|
|
} |
1771
|
|
|
$this->action = $action; |
1772
|
|
|
} |
1773
|
|
|
|
1774
|
|
|
/** |
1775
|
|
|
* Ignore dla logging for a maximum of 900s = 15min |
1776
|
|
|
*/ |
1777
|
|
|
const MAX_IGNORE_DLA_LOG = 900; |
1778
|
|
|
|
1779
|
|
|
/** |
1780
|
|
|
* Update session_action and session_dla (session last used time) |
1781
|
|
|
* |
1782
|
|
|
* @param boolean $update_access_log =false false: dont update egw_access_log table, but set $this->action |
1783
|
|
|
* @return string action as written to egw_access_log.session_action |
1784
|
|
|
*/ |
1785
|
|
|
private function update_dla($update_access_log=false) |
1786
|
|
|
{ |
1787
|
|
|
// This way XML-RPC users aren't always listed as xmlrpc.php |
1788
|
|
|
if (isset($_GET['menuaction'])) |
1789
|
|
|
{ |
1790
|
|
|
list(, $action) = explode('.ajax_exec.template.', $_GET['menuaction']); |
1791
|
|
|
|
1792
|
|
|
if (empty($action)) $action = $_GET['menuaction']; |
1793
|
|
|
} |
1794
|
|
|
else |
1795
|
|
|
{ |
1796
|
|
|
$action = $_SERVER['PHP_SELF']; |
1797
|
|
|
// remove EGroupware path, if not installed in webroot |
1798
|
|
|
$egw_path = $GLOBALS['egw_info']['server']['webserver_url']; |
1799
|
|
|
if ($egw_path[0] != '/') $egw_path = parse_url($egw_path,PHP_URL_PATH); |
1800
|
|
|
if ($action == '/Microsoft-Server-ActiveSync') |
1801
|
|
|
{ |
1802
|
|
|
$action .= '?Cmd='.$_GET['Cmd'].'&DeviceId='.$_GET['DeviceId']; |
1803
|
|
|
} |
1804
|
|
|
elseif ($egw_path) |
1805
|
|
|
{ |
1806
|
|
|
list(,$action) = explode($egw_path,$action,2); |
1807
|
|
|
} |
1808
|
|
|
} |
1809
|
|
|
$this->set_action($action); |
1810
|
|
|
|
1811
|
|
|
// update dla in access-log table, if we have an access-log row (non-anonymous session) |
1812
|
|
|
if ($this->sessionid_access_log && $update_access_log && |
1813
|
|
|
// ignore updates (session creation is written) of *dav, avatar and thumbnail, due to possible high volume of updates |
1814
|
|
|
(!preg_match('#^(/webdav|/groupdav|/api/avatar|/api/thumbnail)\.php#', $this->action) || |
1815
|
|
|
(time() - $_SESSION[self::EGW_SESSION_VAR]['session_logged_dla']) > self::MAX_IGNORE_DLA_LOG) && |
1816
|
|
|
is_object($GLOBALS['egw']->db)) |
1817
|
|
|
{ |
1818
|
|
|
$_SESSION[self::EGW_SESSION_VAR]['session_logged_dla'] = time(); |
1819
|
|
|
|
1820
|
|
|
$GLOBALS['egw']->db->update(self::ACCESS_LOG_TABLE,array( |
1821
|
|
|
'session_dla' => time(), |
1822
|
|
|
'session_action' => $this->action, |
1823
|
|
|
) + ($this->action === '/logout.php' ? array() : array( |
1824
|
|
|
'lo' => null, // just in case it was (automatic) timed out before |
1825
|
|
|
)),array( |
1826
|
|
|
'sessionid' => $this->sessionid_access_log, |
1827
|
|
|
),__LINE__,__FILE__); |
1828
|
|
|
} |
1829
|
|
|
|
1830
|
|
|
$_SESSION[self::EGW_SESSION_VAR]['session_dla'] = time(); |
1831
|
|
|
$_SESSION[self::EGW_SESSION_VAR]['session_action'] = $this->action; |
1832
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__.'() _SESSION['.self::EGW_SESSION_VAR.']='.array2string($_SESSION[self::EGW_SESSION_VAR])); |
1833
|
|
|
|
1834
|
|
|
return $this->action; |
1835
|
|
|
} |
1836
|
|
|
|
1837
|
|
|
/** |
1838
|
|
|
* Update notification_heartbeat time of session |
1839
|
|
|
*/ |
1840
|
|
|
private function update_notification_heartbeat() |
1841
|
|
|
{ |
1842
|
|
|
// update dla in access-log table, if we have an access-log row (non-anonymous session) |
1843
|
|
|
if ($this->sessionid_access_log) |
1844
|
|
|
{ |
1845
|
|
|
$GLOBALS['egw']->db->update(self::ACCESS_LOG_TABLE,array( |
1846
|
|
|
'notification_heartbeat' => time(), |
1847
|
|
|
),array( |
1848
|
|
|
'sessionid' => $this->sessionid_access_log, |
1849
|
|
|
'lo IS NULL', |
1850
|
|
|
),__LINE__,__FILE__); |
1851
|
|
|
} |
1852
|
|
|
} |
1853
|
|
|
|
1854
|
|
|
/** |
1855
|
|
|
* Read the diverse repositories / init classes with data from the just loged in user |
1856
|
|
|
* |
1857
|
|
|
* @return array used to assign to $GLOBALS['egw_info']['user'] |
1858
|
|
|
*/ |
1859
|
|
|
public function read_repositories() |
1860
|
|
|
{ |
1861
|
|
|
$GLOBALS['egw']->acl->__construct($this->account_id); |
1862
|
|
|
$GLOBALS['egw']->preferences->__construct($this->account_id); |
1863
|
|
|
$GLOBALS['egw']->applications->__construct($this->account_id); |
1864
|
|
|
|
1865
|
|
|
$user = $GLOBALS['egw']->accounts->read($this->account_id); |
1866
|
|
|
// set homedirectory from auth_ldap or auth_ads, to be able to use it in vfs |
1867
|
|
|
if (!isset($user['homedirectory'])) |
1868
|
|
|
{ |
1869
|
|
|
// authentication happens in login.php, which does NOT yet create egw-object in session |
1870
|
|
|
// --> need to store homedirectory in session |
1871
|
|
|
if(isset($GLOBALS['auto_create_acct']['homedirectory'])) |
1872
|
|
|
{ |
1873
|
|
|
Cache::setSession(__CLASS__, 'homedirectory', |
1874
|
|
|
$user['homedirectory'] = $GLOBALS['auto_create_acct']['homedirectory']); |
1875
|
|
|
} |
1876
|
|
|
else |
1877
|
|
|
{ |
1878
|
|
|
$user['homedirectory'] = Cache::getSession(__CLASS__, 'homedirectory'); |
1879
|
|
|
} |
1880
|
|
|
} |
1881
|
|
|
$user['preferences'] = $GLOBALS['egw']->preferences->read_repository(); |
1882
|
|
|
if (is_object($GLOBALS['egw']->datetime)) |
1883
|
|
|
{ |
1884
|
|
|
$GLOBALS['egw']->datetime->__construct(); // to set tz_offset from the now read prefs |
1885
|
|
|
} |
1886
|
|
|
$user['apps'] = $GLOBALS['egw']->applications->read_repository(); |
1887
|
|
|
$user['domain'] = $this->account_domain; |
1888
|
|
|
$user['sessionid'] = $this->sessionid; |
1889
|
|
|
$user['kp3'] = $this->kp3; |
1890
|
|
|
$user['session_ip'] = $this->getuser_ip(); |
1891
|
|
|
$user['session_lid'] = $this->account_lid.'@'.$this->account_domain; |
1892
|
|
|
$user['account_id'] = $this->account_id; |
1893
|
|
|
$user['account_lid'] = $this->account_lid; |
1894
|
|
|
$user['userid'] = $this->account_lid; |
1895
|
|
|
$user['passwd'] = $this->passwd; |
1896
|
|
|
|
1897
|
|
|
return $user; |
1898
|
|
|
} |
1899
|
|
|
|
1900
|
|
|
/** |
1901
|
|
|
* Splits a login-name into account_lid and eGW-domain/-instance |
1902
|
|
|
* |
1903
|
|
|
* @param string $login login-name (ie. user@default) |
1904
|
|
|
* @param string &$account_lid returned account_lid (ie. user) |
1905
|
|
|
* @param string &$domain returned domain (ie. domain) |
1906
|
|
|
*/ |
1907
|
|
|
private function split_login_domain($login,&$account_lid,&$domain) |
1908
|
|
|
{ |
1909
|
|
|
$parts = explode('@',$login); |
1910
|
|
|
|
1911
|
|
|
//conference - for strings like [email protected]@default , |
1912
|
|
|
//allows that user have a login that is his e-mail. (viniciuscb) |
1913
|
|
|
if (count($parts) > 1) |
1914
|
|
|
{ |
1915
|
|
|
$probable_domain = array_pop($parts); |
1916
|
|
|
//Last part of login string, when separated by @, is a domain name |
1917
|
|
|
if (in_array($probable_domain,$this->egw_domains)) |
1918
|
|
|
{ |
1919
|
|
|
$got_login = true; |
1920
|
|
|
$domain = $probable_domain; |
1921
|
|
|
$account_lid = implode('@',$parts); |
1922
|
|
|
} |
1923
|
|
|
} |
1924
|
|
|
|
1925
|
|
|
if (!$got_login) |
1926
|
|
|
{ |
1927
|
|
|
$domain = $GLOBALS['egw_info']['server']['default_domain']; |
1928
|
|
|
$account_lid = $login; |
1929
|
|
|
} |
1930
|
|
|
} |
1931
|
|
|
|
1932
|
|
|
/** |
1933
|
|
|
* Create a hash from user and pw |
1934
|
|
|
* |
1935
|
|
|
* Can be used to check setup config user/password inside egroupware: |
1936
|
|
|
* |
1937
|
|
|
* if (Api\Session::user_pw_hash($user,$pw) === $GLOBALS['egw_info']['server']['config_hash']) |
1938
|
|
|
* |
1939
|
|
|
* @param string $user username |
1940
|
|
|
* @param string $password password or md5 hash of password if $allow_password_md5 |
1941
|
|
|
* @param boolean $allow_password_md5 =false can password alread be an md5 hash |
1942
|
|
|
* @return string |
1943
|
|
|
*/ |
1944
|
|
|
static function user_pw_hash($user,$password,$allow_password_md5=false) |
1945
|
|
|
{ |
1946
|
|
|
$password_md5 = $allow_password_md5 && preg_match('/^[a-f0-9]{32}$/',$password) ? $password : md5($password); |
1947
|
|
|
|
1948
|
|
|
$hash = sha1(strtolower($user).$password_md5); |
1949
|
|
|
|
1950
|
|
|
return $hash; |
1951
|
|
|
} |
1952
|
|
|
|
1953
|
|
|
/** |
1954
|
|
|
* Initialise the used session handler |
1955
|
|
|
* |
1956
|
|
|
* @return boolean true if we have a session, false otherwise |
1957
|
|
|
* @throws \ErrorException if there is no PHP session support |
1958
|
|
|
*/ |
1959
|
|
|
public static function init_handler() |
1960
|
|
|
{ |
1961
|
|
|
switch(session_status()) |
1962
|
|
|
{ |
1963
|
|
|
case PHP_SESSION_DISABLED: |
1964
|
|
|
throw new \ErrorException('EGroupware requires PHP session extension!'); |
1965
|
|
|
case PHP_SESSION_NONE: |
1966
|
|
|
ini_set('session.use_cookies',0); // disable the automatic use of cookies, as it uses the path / by default |
1967
|
|
|
session_name(self::EGW_SESSION_NAME); |
1968
|
|
|
if (($sessionid = self::get_sessionid())) |
1969
|
|
|
{ |
1970
|
|
|
session_id($sessionid); |
1971
|
|
|
self::cache_control(); |
1972
|
|
|
$ok = session_start(); |
1973
|
|
|
self::decrypt(); |
1974
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() sessionid=$sessionid, _SESSION[".self::EGW_SESSION_VAR.']='.array2string($_SESSION[self::EGW_SESSION_VAR])); |
1975
|
|
|
return $ok; |
1976
|
|
|
} |
1977
|
|
|
break; |
1978
|
|
|
case PHP_SESSION_ACTIVE: |
1979
|
|
|
return true; // session created by MServer |
1980
|
|
|
} |
1981
|
|
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() no active session!"); |
1982
|
|
|
|
1983
|
|
|
return false; |
1984
|
|
|
} |
1985
|
|
|
|
1986
|
|
|
/** |
1987
|
|
|
* Controling caching and expires header |
1988
|
|
|
* |
1989
|
|
|
* Headers are send based on given parameters or $GLOBALS['egw_info']['flags']['nocachecontrol']: |
1990
|
|
|
* - not set of false --> no caching (default) |
1991
|
|
|
* - true --> private caching by browser (no expires header) |
1992
|
|
|
* - "public" or integer --> public caching with given cache_expire in minutes or php.ini default session_cache_expire |
1993
|
|
|
* |
1994
|
|
|
* @param int $expire =null expiration time in seconds, default $GLOBALS['egw_info']['flags']['nocachecontrol'] or php.ini session.cache_expire |
1995
|
|
|
* @param int $private =null allows to set private caching with given expiration time, by setting it to true |
1996
|
|
|
*/ |
1997
|
|
|
public static function cache_control($expire=null, $private=null) |
1998
|
|
|
{ |
1999
|
|
|
if (is_null($expire) && isset($GLOBALS['egw_info']['flags']['nocachecontrol']) && is_int($GLOBALS['egw_info']['flags']['nocachecontrol'])) |
2000
|
|
|
{ |
2001
|
|
|
$expire = $GLOBALS['egw_info']['flags']['nocachecontrol']; |
2002
|
|
|
} |
2003
|
|
|
// session not yet started: use PHP session_cache_limiter() and session_cache_expires() functions |
2004
|
|
|
if (!isset($_SESSION)) |
2005
|
|
|
{ |
2006
|
|
|
// controling caching and expires header |
2007
|
|
|
if(!isset($expire) && (!isset($GLOBALS['egw_info']['flags']['nocachecontrol']) || |
2008
|
|
|
!$GLOBALS['egw_info']['flags']['nocachecontrol'])) |
2009
|
|
|
{ |
2010
|
|
|
session_cache_limiter('nocache'); |
2011
|
|
|
} |
2012
|
|
|
elseif (isset($expire) || $GLOBALS['egw_info']['flags']['nocachecontrol'] === 'public' || is_int($GLOBALS['egw_info']['flags']['nocachecontrol'])) |
2013
|
|
|
{ |
2014
|
|
|
// allow public caching: proxys, cdns, ... |
2015
|
|
|
if (isset($expire)) |
2016
|
|
|
{ |
2017
|
|
|
session_cache_expire((int)ceil($expire/60)); // in minutes |
2018
|
|
|
} |
2019
|
|
|
session_cache_limiter($private ? 'private' : 'public'); |
2020
|
|
|
} |
2021
|
|
|
else |
2022
|
|
|
{ |
2023
|
|
|
// allow caching by browser |
2024
|
|
|
session_cache_limiter('private_no_expire'); |
2025
|
|
|
} |
2026
|
|
|
} |
2027
|
|
|
// session already started |
2028
|
|
|
if (isset($_SESSION)) |
2029
|
|
|
{ |
2030
|
|
|
if ($expire && (session_cache_limiter() !== ($expire===true?'private_no_expire':'public') || |
2031
|
|
|
is_int($expire) && $expire/60 !== session_cache_expire())) |
2032
|
|
|
{ |
2033
|
|
|
$file = $line = null; |
2034
|
|
|
if (headers_sent($file, $line)) |
2035
|
|
|
{ |
2036
|
|
|
error_log(__METHOD__."($expire) called, but header already sent in $file: $line"); |
2037
|
|
|
return; |
2038
|
|
|
} |
2039
|
|
|
if($expire === true) // same behavior as session_cache_limiter('private_no_expire') |
2040
|
|
|
{ |
2041
|
|
|
header('Cache-Control: private, max-age='.(60*session_cache_expire())); |
2042
|
|
|
header_remove('Expires'); |
2043
|
|
|
} |
2044
|
|
|
elseif ($private) |
|
|
|
|
2045
|
|
|
{ |
2046
|
|
|
header('Cache-Control: private, max-age='.$expire); |
2047
|
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expire) . ' GMT'); |
2048
|
|
|
} |
2049
|
|
|
else |
2050
|
|
|
{ |
2051
|
|
|
header('Cache-Control: public, max-age='.$expire); |
2052
|
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expire) . ' GMT'); |
2053
|
|
|
} |
2054
|
|
|
// remove Pragma header, might be set by old header |
2055
|
|
|
if (function_exists('header_remove')) // PHP 5.3+ |
2056
|
|
|
{ |
2057
|
|
|
header_remove('Pragma'); |
2058
|
|
|
} |
2059
|
|
|
else |
2060
|
|
|
{ |
2061
|
|
|
header('Pragma:'); |
2062
|
|
|
} |
2063
|
|
|
} |
2064
|
|
|
} |
2065
|
|
|
} |
2066
|
|
|
|
2067
|
|
|
/** |
2068
|
|
|
* Get a session list (of the current instance) |
2069
|
|
|
* |
2070
|
|
|
* @param int $start |
2071
|
|
|
* @param string $sort ='DESC' ASC or DESC |
2072
|
|
|
* @param string $order ='session_dla' session_lid, session_id, session_started, session_logintime, session_action, or (default) session_dla |
2073
|
|
|
* @param boolean $all_no_sort =False skip sorting and limiting to maxmatchs if set to true |
2074
|
|
|
* @param array $filter =array() extra filter for sessions |
2075
|
|
|
* @return array with sessions (values for keys as in $sort) |
2076
|
|
|
*/ |
2077
|
|
|
public static function session_list($start,$sort='DESC',$order='session_dla',$all_no_sort=False,array $filter=array()) |
2078
|
|
|
{ |
2079
|
|
|
$sessions = array(); |
2080
|
|
|
if (!preg_match('/^[a-z0-9_ ,]+$/i',$order_by=$order.' '.$sort) || $order_by == ' ') |
2081
|
|
|
{ |
2082
|
|
|
$order_by = 'session_dla DESC'; |
2083
|
|
|
} |
2084
|
|
|
$filter['lo'] = null; |
2085
|
|
|
$filter[] = 'account_id>0'; |
2086
|
|
|
$filter[] = 'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']); |
2087
|
|
|
$filter[] = '(notification_heartbeat IS NULL OR notification_heartbeat > '.self::heartbeat_limit().')'; |
2088
|
|
|
foreach($GLOBALS['egw']->db->select(self::ACCESS_LOG_TABLE, '*', $filter, __LINE__, __FILE__, |
2089
|
|
|
$all_no_sort ? false : $start, 'ORDER BY '.$order_by) as $row) |
2090
|
|
|
{ |
2091
|
|
|
$sessions[$row['sessionid']] = $row; |
2092
|
|
|
} |
2093
|
|
|
return $sessions; |
2094
|
|
|
} |
2095
|
|
|
|
2096
|
|
|
/** |
2097
|
|
|
* Query number of sessions (not more then once every N secs) |
2098
|
|
|
* |
2099
|
|
|
* @param array $filter =array() extra filter for sessions |
2100
|
|
|
* @return int number of active sessions |
2101
|
|
|
*/ |
2102
|
|
|
public static function session_count(array $filter=array()) |
2103
|
|
|
{ |
2104
|
|
|
$filter['lo'] = null; |
2105
|
|
|
$filter[] = 'account_id>0'; |
2106
|
|
|
$filter[] = 'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']); |
2107
|
|
|
$filter[] = '(notification_heartbeat IS NULL OR notification_heartbeat > '.self::heartbeat_limit().')'; |
2108
|
|
|
return $GLOBALS['egw']->db->select(self::ACCESS_LOG_TABLE, 'COUNT(*)', $filter, __LINE__, __FILE__)->fetchColumn(); |
2109
|
|
|
} |
2110
|
|
|
|
2111
|
|
|
/** |
2112
|
|
|
* Get limit / latest time of heartbeat for session to be active |
2113
|
|
|
* |
2114
|
|
|
* @return int TS in server-time |
2115
|
|
|
*/ |
2116
|
|
|
public static function heartbeat_limit() |
2117
|
|
|
{ |
2118
|
|
|
static $limit=null; |
2119
|
|
|
|
2120
|
|
|
if (is_null($limit)) |
2121
|
|
|
{ |
2122
|
|
|
$config = Config::read('notifications'); |
2123
|
|
|
if (!($popup_poll_interval = $config['popup_poll_interval'])) |
2124
|
|
|
{ |
2125
|
|
|
$popup_poll_interval = 60; |
2126
|
|
|
} |
2127
|
|
|
$limit = (int)(time() - $popup_poll_interval-10); // 10s grace periode |
2128
|
|
|
} |
2129
|
|
|
return $limit; |
2130
|
|
|
} |
2131
|
|
|
|
2132
|
|
|
/** |
2133
|
|
|
* Check if given user can be reached via notifications |
2134
|
|
|
* |
2135
|
|
|
* Checks if notifications callback checked in not more then heartbeat_limit() seconds ago |
2136
|
|
|
* |
2137
|
|
|
* @param int $account_id |
2138
|
|
|
* @param int number of active sessions of given user with notifications running |
2139
|
|
|
*/ |
2140
|
|
|
public static function notifications_active($account_id) |
2141
|
|
|
{ |
2142
|
|
|
return $GLOBALS['egw']->db->select(self::ACCESS_LOG_TABLE, 'COUNT(*)', array( |
2143
|
|
|
'lo' => null, |
2144
|
|
|
'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']), |
2145
|
|
|
'account_id' => $account_id, |
2146
|
|
|
'notification_heartbeat > '.self::heartbeat_limit(), |
2147
|
|
|
), __LINE__, __FILE__)->fetchColumn(); |
2148
|
|
|
} |
2149
|
|
|
} |
2150
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.