Issues (4868)

admin/inc/class.admin_accesslog.inc.php (5 issues)

1
<?php
2
/**
3
 * EGroupware admin - access- and session-log
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package admin
8
 * @copyright (c) 2009-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 */
11
12
use EGroupware\Api;
13
use EGroupware\Api\Etemplate;
14
15
/**
16
 * Show EGroupware access- and session-log
17
 */
18
class admin_accesslog
19
{
20
	/**
21
	 * Which methods of this class can be called as menuation
22
	 *
23
	 * @var array
24
	 */
25
	public $public_functions = array(
26
		'index' => true,
27
		'sessions' => true,
28
	);
29
30
	/**
31
	 * Our storage object
32
	 *
33
	 * @var Api\Storage\Base
34
	 */
35
	protected $so;
36
37
	/**
38
	 * Name of our table
39
	 */
40
	const TABLE = 'egw_access_log';
41
	/**
42
	 * Name of app the table is registered
43
	 */
44
	const APP = 'phpgwapi';
45
46
	/**
47
	 * Constructor
48
	 *
49
	 */
50
	function __construct()
51
	{
52
		$this->so = new Api\Storage\Base(self::APP,self::TABLE,null,'',true);
53
		$this->so->timestamps = array('li', 'lo', 'session_dla', 'notification_heartbeat');
54
	}
55
56
	/**
57
	 * query rows for the nextmatch widget
58
	 *
59
	 * @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter' and
60
	 *	'session_list' true: all sessions, false: whole access-log, 'active': only sessions with session-status active (browser, no sync)
61
	 * @param array &$rows returned rows/competitions
62
	 * @param array &$readonlys eg. to disable buttons based on acl, not use here, maybe in a derived class
63
	 * @return int total number of rows
64
	 */
65
	function get_rows($query,&$rows,&$readonlys)
66
	{
67
		$heartbeat_limit = Api\Session::heartbeat_limit();
68
69
		if ($query['session_list'])	// filter active sessions
70
		{
71
			$query['col_filter']['lo'] = null;	// not logged out
72
			$query['col_filter'][0] = 'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']);
73
			switch((string)$query['session_list'])
74
			{
75
				case 'active':	// remove status != 'active', eg. CalDAV/eSync
76
					$query['col_filter'][1] = "notification_heartbeat > $heartbeat_limit";
77
					$query['col_filter'][3] = "session_php NOT LIKE '% %'";	// remove blocked, bad login, etc
78
					break;
79
				default:
80
					$query['col_filter'][1] = "(notification_heartbeat IS NULL OR notification_heartbeat > $heartbeat_limit)";
81
					break;
82
			}
83
			$query['col_filter'][2] = 'account_id>0';
84
		}
85
		$total = $this->so->get_rows($query,$rows,$readonlys);
86
87
		$heartbeat_limit_user = Api\DateTime::server2user($heartbeat_limit, 'ts');
88
89
		foreach($rows as &$row)
90
		{
91
			$row['sessionstatus'] = 'success';
92
			if ($row['notification_heartbeat'] > $heartbeat_limit_user)
93
			{
94
				$row['sessionstatus'] = 'active';
95
			}
96
			if (stripos($row['session_php'],'blocked') !== false ||
97
				stripos($row['session_php'],'bad login') !== false ||
98
				strpos($row['session_php'],' ') !== false)
99
			{
100
				$row['sessionstatus'] = $row['session_php'];
101
			}
102
			if ($row['lo']) {
103
				$row['total'] = ($row['lo'] - $row['li']) / 60;
104
				$row['sessionstatus'] = 'logged out';
105
			}
106
			// eg. for bad login or password
107
			if (!$row['account_id']) $row['alt_loginid'] = ($row['loginid']?$row['loginid']:lang('none'));
108
109
			// do not allow to kill or select own session
110
			if ($GLOBALS['egw']->session->sessionid_access_log == $row['sessionid'] && $query['session_list'])
111
			{
112
				$row['class'] .= ' rowNoDelete ';
113
			}
114
			// do not allow to delete access log off active sessions
115
			if (!$row['lo'] && $row['session_dla'] > time()-$GLOBALS['egw_info']['server']['sessions_timeout'] &&
116
				in_array($row['sessionstatus'], array('active', 'success')) && !$query['session_list'])
117
			{
118
				$row['class'] .= ' rowNoDelete ';
119
			}
120
			$row['sessionstatus'] = lang($row['sessionstatus']);
121
			unset($row['session_php']);	// for security reasons, do NOT give real PHP sessionid to UI
122
123
			$row['os_browser'] = Api\Header\UserAgent::osBrowser($row['user_agent']);
124
		}
125
		if ($query['session_list'])
126
		{
127
			$rows['no_total'] = $rows['no_lo'] = true;
128
		}
129
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Admin').' - '.
130
			($query['session_list'] ? lang('View sessions') : lang('View Access Log')).
131
			($query['col_filter']['account_id'] ? ': '.Api\Accounts::username($query['col_filter']['account_id']) : '');
132
133
		return $total;
134
	}
135
136
	/**
137
	 * Display the access log or session list
138
	 *
139
	 * @param array $content =null
140
	 * @param string $msg =''
141
	 * @param boolean $sessions_list =false
142
	 */
143
	function index(array $content=null, $msg='', $sessions_list=false)
144
	{
145
146
		if (is_array($content)) $sessions_list = $content['nm']['session_list'];
147
148
		// check if user has access to requested functionality
149
		if ($GLOBALS['egw']->acl->check($sessions_list ? 'current_sessions' : 'access_log_acces',1,'admin'))
150
		{
151
			$GLOBALS['egw']->redirect_link('/index.php');
152
		}
153
154
		if(!isset($content))
155
		{
156
			$content['nm'] = array(
157
				'get_rows'       =>	'admin.admin_accesslog.get_rows',	// I  method/callback to request the data for the rows eg. 'notes.bo.get_rows'
158
				'no_filter'      => True,	// I  disable the 1. filter
159
				'no_filter2'     => True,	// I  disable the 2. filter (params are the same as for filter)
160
				'no_cat'         => True,	// I  disable the cat-selectbox
161
				'header_left'    =>	false,	// I  template to show left of the range-value, left-aligned (optional)
162
				'header_right'   =>	false,	// I  template to show right of the range-value, right-aligned (optional)
163
				'never_hide'     => True,	// I  never hide the nextmatch-line if less then maxmatch entries
164
				'lettersearch'   => false,	// I  show a lettersearch
165
				'start'          =>	0,		// IO position in list
166
				'order'          =>	'li',	// IO name of the column to sort after (optional for the sortheaders)
167
				'sort'           =>	'DESC',	// IO direction of the sort: 'ASC' or 'DESC'
168
				'default_cols'   => '!session_action',	// I  columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
169
				'csv_fields'     =>	false,	// I  false=disable csv export, true or unset=enable it with auto-detected fieldnames,
170
								//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
171
				'actions'		=> $this->get_actions($sessions_list),
172
				'placeholder_actions' => false,
173
				'row_id'			=> 'sessionid',
174
			);
175
			if ((int)$_GET['account_id'])
176
			{
177
				$content['nm']['col_filter']['account_id'] = (int)$_GET['account_id'];
178
			}
179
			if ($sessions_list)
180
			{
181
				$content['nm']['order'] = 'session_dla';
182
				$content['nm']['options-selectcols'] = array(
183
					'lo' => false,
184
					'total' => false,
185
				);
186
			}
187
			$content['nm']['session_list'] = $sessions_list;
188
		}
189
		//error_log(__METHOD__. ' accesslog =>' . array2string($content['nm']['selected']));
190
		if ($content['nm']['action'])
191
		{
192
			if ($content['nm']['select_all'])
193
			{
194
				// get the whole selection
195
				$query = array(
196
					'search' => $content['nm']['search'],
197
					'col_filter' => $content['nm']['col_filter']
198
				);
199
200
				@set_time_limit(0);			// switch off the execution time limit, as it's for big selections to small
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

200
				/** @scrutinizer ignore-unhandled */ @set_time_limit(0);			// switch off the execution time limit, as it's for big selections to small

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
201
				$query['num_rows'] = -1;	// all
202
				$all = $readonlys = array();
203
				$this->get_rows($query,$all,$readonlys);
204
				$content['nm']['selected'] = array();
205
				foreach($all as $session)
206
				{
207
					$content['nm']['selected'][] = $session[$content['nm']['row_id']];
208
				}
209
			}
210
			if (!count($content['nm']['selected']) && !$content['nm']['select_all'])
211
			{
212
				$msg = lang('You need to select some entries first!');
213
			}
214
			else
215
			{
216
				$success = $failed = $action = $action_msg = null;
0 ignored issues
show
The assignment to $action is dead and can be removed.
Loading history...
217
				if ($this->action($content['nm']['action'],$content['nm']['selected']
218
					,$success,$failed,$action_msg,$msg))
0 ignored issues
show
The call to admin_accesslog::action() has too many arguments starting with $msg. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

218
				if ($this->/** @scrutinizer ignore-call */ action($content['nm']['action'],$content['nm']['selected']

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
219
				{ // In case of action success
220
					switch ($action_msg)
221
					{
222
						case'deleted':
223
							$msg = lang('%1 log entries deleted.',$success);
0 ignored issues
show
The call to lang() has too many arguments starting with $success. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
							$msg = /** @scrutinizer ignore-call */ lang('%1 log entries deleted.',$success);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
224
							break;
225
						case'killed':
226
							$msg = lang('%1 sessions killed',$success);
227
					}
228
				}
229
				elseif($failed) // In case of action failiure
230
				{
231
					switch ($action_msg)
232
					{
233
						case'deleted':
234
							$msg = lang('Error deleting log entry!');
235
							break;
236
						case'killed':
237
							$msg = lang('Permission denied!');
238
					}
239
				}
240
			}
241
		}
242
243
		$content['msg'] = $msg;
244
		$content['percent'] = 100.0 * $GLOBALS['egw']->db->query(
245
			'SELECT ((SELECT COUNT(*) FROM '.self::TABLE.' WHERE lo != 0) / COUNT(*)) FROM '.self::TABLE,
246
			__LINE__,__FILE__)->fetchColumn();
247
248
		$tmpl = new Etemplate('admin.accesslog');
249
		$tmpl->exec('admin.admin_accesslog.index', $content, array(), $readonlys, array(
250
			'nm' => $content['nm'],
251
		));
252
	}
253
254
	/**
255
	 * Apply an action to multiple logs
256
	 *
257
	 * @param type $action
258
	 * @param type $checked
259
	 * @param type $use_all
260
	 * @param type $success
261
	 * @param int $failed
262
	 * @param type $action_msg
263
	 * @return type number of failed
264
	 */
265
	function action($action,$checked,&$success,&$failed,&$action_msg)
266
	{
267
		$success = $failed = 0;
268
		//error_log(__METHOD__.'selected:' . array2string($checked). 'action:' . $action);
269
		switch ($action)
270
		{
271
			case "delete":
272
				$action_msg = "deleted";
273
				$del_msg= $this->so->delete(array('sessionid' => $checked));
274
				if ($checked && $del_msg)
275
				{
276
					$success = $del_msg;
277
				}
278
				else
279
				{
280
					$failed ++;
281
				}
282
				break;
283
			case "kill":
284
				$action_msg = "killed";
285
				$sessionid = $checked;
286
				if (($key = array_search($GLOBALS['egw']->session->sessionid_access_log, $sessionid)))
287
				{
288
						unset($sessionid[$key]);	// dont allow to kill own sessions
289
				}
290
				if ($GLOBALS['egw']->acl->check('current_sessions',8,'admin'))
291
				{
292
					$failed ++;
293
				}
294
				else
295
				{
296
					foreach((array)$sessionid as $id)
297
					{
298
						$GLOBALS['egw']->session->destroy($id);
299
					}
300
					$success= count($sessionid);
301
				}
302
				break;
303
		}
304
		return !$failed;
305
	}
306
307
	/**
308
	 * Get actions / context menu for index
309
	 *
310
	 * Changes here, require to log out, as $content['nm'] get stored in session!
311
	 *
312
	 * @return array see nextmatch_widget::egw_actions()
313
	 */
314
	private static function get_actions($sessions_list)
315
	{
316
		$group = 0;
317
		if ($sessions_list)
318
		{
319
		//	error_log(__METHOD__. $sessions_list);
320
			$actions= array(
321
				'kill' => array(
322
					'caption' => 'Kill',
323
					'confirm' => 'Kill this session',
324
					'confirm_multiple' => 'Kill these sessions',
325
					'group' => $group,
326
					'disableClass' => 'rowNoDelete',
327
				),
328
			);
329
330
		}
331
		else
332
		{
333
			$actions= array(
334
				'delete' => array(
335
					'caption' => 'Delete',
336
					'confirm' => 'Delete this entry',
337
					'confirm_multiple' => 'Delete these entries',
338
					'group' => $group,
339
					'disableClass' => 'rowNoDelete',
340
				),
341
			);
342
		}
343
		// Automatic select all doesn't work with only 1 action
344
		$actions['select_all'] = array(
345
			'caption' => 'Select all',
346
			//'checkbox' => true,
347
			'hint' => 'Select all entries',
348
			'enabled' => true,
349
			'shortcut' => array(
350
				'keyCode'	=>	65, // A
351
				'ctrl'		=>	true,
352
				'caption'	=> lang('Ctrl').'+A'
353
			),
354
			'group' => $group++,
355
		);
356
		return $actions;
357
	}
358
359
	/**
360
	 * Display session list
361
	 *
362
	 * @param array $content =null
363
	 * @param string $msg =''
364
	 */
365
	function sessions(array $content=null, $msg='')
366
	{
367
		return $this->index($content, $msg, true);
0 ignored issues
show
Are you sure the usage of $this->index($content, $msg, true) targeting admin_accesslog::index() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
368
	}
369
}
370