egroupware_official/admin/inc/class.admin_accesslog.inc.php

384 lines
12 KiB
PHP

<?php
/**
* EGroupware admin - access- and session-log
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package admin
* @copyright (c) 2009-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
use EGroupware\Api;
use EGroupware\Api\Etemplate;
/**
* Show EGroupware access- and session-log
*/
class admin_accesslog
{
/**
* Which methods of this class can be called as menuation
*
* @var array
*/
public $public_functions = array(
'index' => true,
'sessions' => true,
);
/**
* Our storage object
*
* @var Api\Storage\Base
*/
protected $so;
/**
* Name of our table
*/
const TABLE = 'egw_access_log';
/**
* Name of app the table is registered
*/
const APP = 'phpgwapi';
/**
* Constructor
*
*/
function __construct()
{
$this->so = new Api\Storage\Base(self::APP,self::TABLE,null,'',true);
$this->so->timestamps = array('li', 'lo', 'session_dla', 'notification_heartbeat');
}
/**
* query rows for the nextmatch widget
*
* @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter' and
* 'session_list' true: all sessions, false: whole access-log, 'active': only sessions with session-status active (browser, no sync)
* @param array &$rows returned rows/competitions
* @param array &$readonlys eg. to disable buttons based on acl, not use here, maybe in a derived class
* @return int total number of rows
*/
function get_rows($query,&$rows,&$readonlys)
{
$heartbeat_limit = Api\Session::heartbeat_limit();
if ($query['session_list']) // filter active sessions
{
$query['col_filter']['lo'] = null; // not logged out
$query['col_filter'][0] = 'session_dla > '.(int)(time() - $GLOBALS['egw_info']['server']['sessions_timeout']);
// for push via fallback (no native push) we use the heartbeat (constant polling of notification app)
if (Api\Json\Push::onlyFallback())
{
$active_query = "notification_heartbeat > $heartbeat_limit";
}
else
{
// for native push we ask the push-server who is active
$online = (array)Api\Json\Push::online();
$active_query = $GLOBALS['egw']->db->expression(self::TABLE, ['account_id' => $online]);
}
switch((string)$query['session_list'])
{
case 'active': // remove status != 'active', eg. CalDAV/eSync
$query['col_filter'][1] = $active_query;
$query['col_filter'][3] = "session_php NOT LIKE '% %'"; // remove blocked, bad login, etc
break;
default:
$query['col_filter'][1] = "(notification_heartbeat IS NULL OR $active_query)";
break;
}
$query['col_filter'][2] = 'account_id>0';
}
$total = $this->so->get_rows($query,$rows,$readonlys);
$heartbeat_limit_user = Api\DateTime::server2user($heartbeat_limit, 'ts');
foreach($rows as &$row)
{
$row['sessionstatus'] = 'success';
if (isset($online) ?
// we still need to check notification_heartbeat to distinguish from non-interactive session like *DAV
isset($row['notification_heartbeat']) && in_array($row['account_id'], $online) :
$row['notification_heartbeat'] > $heartbeat_limit_user)
{
$row['sessionstatus'] = 'active';
}
if (stripos($row['session_php'],'blocked') !== false ||
stripos($row['session_php'],'bad login') !== false ||
strpos($row['session_php'],' ') !== false)
{
$row['sessionstatus'] = $row['session_php'];
}
if ($row['lo']) {
$row['total'] = ($row['lo'] - $row['li']) / 60;
$row['sessionstatus'] = 'logged out';
}
// eg. for bad login or password
if (!$row['account_id']) $row['alt_loginid'] = ($row['loginid']?$row['loginid']:lang('none'));
// do not allow to kill or select own session
if ($GLOBALS['egw']->session->sessionid_access_log == $row['sessionid'] && $query['session_list'])
{
$row['class'] .= ' rowNoDelete ';
}
// do not allow to delete access log off active sessions
if (!$row['lo'] && $row['session_dla'] > time()-$GLOBALS['egw_info']['server']['sessions_timeout'] &&
in_array($row['sessionstatus'], array('active', 'success')) && !$query['session_list'])
{
$row['class'] .= ' rowNoDelete ';
}
$row['sessionstatus'] = lang($row['sessionstatus']);
unset($row['session_php']); // for security reasons, do NOT give real PHP sessionid to UI
$row['os_browser'] = Api\Header\UserAgent::osBrowser($row['user_agent']);
}
if ($query['session_list'])
{
$rows['no_total'] = $rows['no_lo'] = true;
}
$GLOBALS['egw_info']['flags']['app_header'] = lang('Admin').' - '.
($query['session_list'] ? lang('View sessions') : lang('View Access Log')).
($query['col_filter']['account_id'] ? ': '.Api\Accounts::username($query['col_filter']['account_id']) : '');
return $total;
}
/**
* Display the access log or session list
*
* @param array $content =null
* @param string $msg =''
* @param boolean $sessions_list =false
*/
function index(array $content=null, $msg='', $sessions_list=false)
{
if (is_array($content)) $sessions_list = $content['nm']['session_list'];
// check if user has access to requested functionality
if ($GLOBALS['egw']->acl->check($sessions_list ? 'current_sessions' : 'access_log_acces',1,'admin'))
{
$GLOBALS['egw']->redirect_link('/index.php');
}
if(!isset($content))
{
$content['nm'] = array(
'get_rows' => 'admin.admin_accesslog.get_rows', // I method/callback to request the data for the rows eg. 'notes.bo.get_rows'
'no_filter' => True, // I disable the 1. filter
'no_filter2' => True, // I disable the 2. filter (params are the same as for filter)
'no_cat' => True, // I disable the cat-selectbox
'header_left' => false, // I template to show left of the range-value, left-aligned (optional)
'header_right' => false, // I template to show right of the range-value, right-aligned (optional)
'never_hide' => True, // I never hide the nextmatch-line if less then maxmatch entries
'lettersearch' => false, // I show a lettersearch
'start' => 0, // IO position in list
'order' => 'li', // IO name of the column to sort after (optional for the sortheaders)
'sort' => 'DESC', // IO direction of the sort: 'ASC' or 'DESC'
'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
'csv_fields' => false, // I false=disable csv export, true or unset=enable it with auto-detected fieldnames,
//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
'actions' => $this->get_actions($sessions_list),
'placeholder_actions' => false,
'row_id' => 'sessionid',
);
if ((int)$_GET['account_id'])
{
$content['nm']['col_filter']['account_id'] = (int)$_GET['account_id'];
}
if ($sessions_list)
{
$content['nm']['order'] = 'session_dla';
$content['nm']['options-selectcols'] = array(
'lo' => false,
'total' => false,
);
}
$content['nm']['session_list'] = $sessions_list;
}
//error_log(__METHOD__. ' accesslog =>' . array2string($content['nm']['selected']));
if ($content['nm']['action'])
{
if ($content['nm']['select_all'])
{
// get the whole selection
$query = array(
'search' => $content['nm']['search'],
'col_filter' => $content['nm']['col_filter']
);
@set_time_limit(0); // switch off the execution time limit, as it's for big selections to small
$query['num_rows'] = -1; // all
$all = $readonlys = array();
$this->get_rows($query,$all,$readonlys);
$content['nm']['selected'] = array();
foreach($all as $session)
{
$content['nm']['selected'][] = $session[$content['nm']['row_id']];
}
}
if (!count($content['nm']['selected']) && !$content['nm']['select_all'])
{
$msg = lang('You need to select some entries first!');
}
else
{
$success = $failed = $action = $action_msg = null;
if ($this->action($content['nm']['action'],$content['nm']['selected']
,$success,$failed,$action_msg,$msg))
{ // In case of action success
switch ($action_msg)
{
case'deleted':
$msg = lang('%1 log entries deleted.',$success);
break;
case'killed':
$msg = lang('%1 sessions killed',$success);
}
}
elseif($failed) // In case of action failiure
{
switch ($action_msg)
{
case'deleted':
$msg = lang('Error deleting log entry!');
break;
case'killed':
$msg = lang('Permission denied!');
}
}
}
}
$content['msg'] = $msg;
$content['percent'] = 100.0 * $GLOBALS['egw']->db->query(
'SELECT ((SELECT COUNT(*) FROM '.self::TABLE.' WHERE lo != 0) / COUNT(*)) FROM '.self::TABLE,
__LINE__,__FILE__)->fetchColumn();
$tmpl = new Etemplate('admin.accesslog');
$tmpl->exec('admin.admin_accesslog.index', $content, array(), $readonlys, array(
'nm' => $content['nm'],
));
}
/**
* Apply an action to multiple logs
*
* @param type $action
* @param type $checked
* @param type $use_all
* @param type $success
* @param int $failed
* @param type $action_msg
* @return type number of failed
*/
function action($action,$checked,&$success,&$failed,&$action_msg)
{
$success = $failed = 0;
//error_log(__METHOD__.'selected:' . array2string($checked). 'action:' . $action);
switch ($action)
{
case "delete":
$action_msg = "deleted";
$del_msg= $this->so->delete(array('sessionid' => $checked));
if ($checked && $del_msg)
{
$success = $del_msg;
}
else
{
$failed ++;
}
break;
case "kill":
$action_msg = "killed";
$sessionid = $checked;
if (($key = array_search($GLOBALS['egw']->session->sessionid_access_log, $sessionid)))
{
unset($sessionid[$key]); // dont allow to kill own sessions
}
if ($GLOBALS['egw']->acl->check('current_sessions',8,'admin'))
{
$failed ++;
}
else
{
foreach((array)$sessionid as $id)
{
$GLOBALS['egw']->session->destroy($id);
}
$success= count($sessionid);
}
break;
}
return !$failed;
}
/**
* Get actions / context menu for index
*
* Changes here, require to log out, as $content['nm'] get stored in session!
*
* @return array see nextmatch_widget::egw_actions()
*/
private static function get_actions($sessions_list)
{
$group = 0;
if ($sessions_list)
{
// error_log(__METHOD__. $sessions_list);
$actions= array(
'kill' => array(
'caption' => 'Kill',
'confirm' => 'Kill this session',
'confirm_multiple' => 'Kill these sessions',
'group' => $group,
'disableClass' => 'rowNoDelete',
),
);
}
else
{
$actions= array(
'delete' => array(
'caption' => 'Delete',
'confirm' => 'Delete this entry',
'confirm_multiple' => 'Delete these entries',
'group' => $group,
'disableClass' => 'rowNoDelete',
),
);
}
// Automatic select all doesn't work with only 1 action
$actions['select_all'] = array(
'caption' => 'Select all',
//'checkbox' => true,
'hint' => 'Select all entries',
'enabled' => true,
'shortcut' => array(
'keyCode' => 65, // A
'ctrl' => true,
'caption' => lang('Ctrl').'+A'
),
'group' => $group++,
);
return $actions;
}
/**
* Display session list
*
* @param array $content =null
* @param string $msg =''
*/
function sessions(array $content=null, $msg='')
{
return $this->index($content, $msg, true);
}
}