84cc52fe29451d247f9399de8f156a187e398201
[tine20] / tine20 / Tinebase / AccessLog.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  */ 
10
11 /**
12  * this class provides functions to get, add and remove entries from/to the access log
13  * 
14  * @package     Tinebase
15  */
16 class Tinebase_AccessLog extends Tinebase_Controller_Record_Abstract
17 {
18     /**
19      * @var Tinebase_Backend_Sql
20      */
21     protected $_backend;
22     
23     /**
24      * holds the instance of the singleton
25      *
26      * @var Tinebase_AccessLog
27      */
28     private static $_instance = NULL;
29     
30     /**
31      * the constructor
32      *
33      */
34     private function __construct()
35     {
36         $this->_modelName = 'Tinebase_Model_AccessLog';
37         $this->_omitModLog = TRUE;
38         $this->_doContainerACLChecks = FALSE;
39         
40         $this->_backend = new Tinebase_Backend_Sql(array(
41             'modelName' => $this->_modelName, 
42             'tableName' => 'access_log',
43         ));
44     }
45     
46     /**
47      * the singleton pattern
48      *
49      * @return Tinebase_AccessLog
50      */
51     public static function getInstance() 
52     {
53         if (self::$_instance === NULL) {
54             self::$_instance = new Tinebase_AccessLog;
55         }
56         
57         return self::$_instance;
58     }
59
60     /**
61      * returns false if not blocked and number of failed logins if
62      *
63      * @param Tinebase_Model_FullUser  $_user
64      * @param Tinebase_Model_AccessLog $_accessLog
65      * @return bool|integer
66      */
67     public function isUserAgentBlocked(Tinebase_Model_FullUser $_user, Tinebase_Model_AccessLog $_accessLog)
68     {
69         if ($this->_tooManyUserAgents($_user)) {
70             return true;
71         }
72
73         $db = $this->_backend->getAdapter();
74         $dbCommand = Tinebase_Backend_Sql_Command::factory($db);
75         $select = $db->select()
76             ->from($this->_backend->getTablePrefix() . $this->_backend->getTableName(), new Zend_Db_Expr('count(*)'))
77             ->where( $db->quoteIdentifier('account_id') . ' = ?', $_user->getId() )
78             ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval('MINUTE', '1'))
79             ->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT)
80             ->where( $db->quoteIdentifier('user_agent') . ' = ?', $_accessLog->user_agent);
81
82         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
83
84         $stmt = $db->query($select);
85         $count = $stmt->fetchColumn();
86         $stmt->closeCursor();
87
88         if ($count > 0) {
89             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
90                 . ' UserAgent blocked. Login failures: ' . $count);
91             return true;
92         }
93         return false;
94     }
95
96     /**
97      * check if user connected with too many user agent during the last hour
98      *
99      * @param Tinebase_Model_FullUser  $_user
100      * @param int $numberOfAllowedUserAgents
101      * @return bool
102      */
103     protected function _tooManyUserAgents($_user, $numberOfAllowedUserAgents = 3)
104     {
105         $result = false;
106         $db = $this->_backend->getAdapter();
107         $dbCommand = Tinebase_Backend_Sql_Command::factory($db);
108         $select = $db->select()
109             ->distinct(true)
110             ->from($this->_backend->getTablePrefix() . $this->_backend->getTableName(), 'user_agent')
111             ->where( $db->quoteIdentifier('account_id') . ' = ?', $_user->getId() )
112             ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval('HOUR', '1'))
113             ->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT)
114             ->limit(10);
115
116         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
117
118         $stmt = $db->query($select);
119
120         if ($stmt->columnCount() > $numberOfAllowedUserAgents) {
121             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
122                 . ' More than ' . $numberOfAllowedUserAgents . ' different UserAgents? we don\'t trust you!');
123             $result = true;
124         }
125         $stmt->closeCursor();
126         return $result;
127     }
128
129     /**
130      * get previous access log entry
131      * 
132      * @param Tinebase_Model_AccessLog $accessLog
133      * @throws Tinebase_Exception_NotFound
134      * @return Tinebase_Model_AccessLog
135      */
136     public function getPreviousAccessLog(Tinebase_Model_AccessLog $accessLog)
137     {
138         $previousAccessLog = $this->search(
139             new Tinebase_Model_AccessLogFilter(array(
140                 array(
141                     'field'    => 'ip',
142                     'operator' => 'equals',
143                     'value'    => $accessLog->ip
144                 ),
145                 array(
146                     'field'    => 'account_id',
147                     'operator' => 'equals',
148                     'value'    => $accessLog->account_id
149                 ),
150                 array(
151                     'field'    => 'result',
152                     'operator' => 'equals',
153                     'value'    => Tinebase_Auth::SUCCESS
154                 ),
155                 array(
156                     'field'    => 'clienttype',
157                     'operator' => 'equals',
158                     'value'    => $accessLog->clienttype
159                 ),
160                 array(
161                     'field'    => 'user_agent',
162                     'operator' => 'equals',
163                     'value'    => $accessLog->user_agent
164                 ),
165                 array(
166                     'field'    => 'lo',
167                     'operator' => 'after',
168                     'value'    => Tinebase_DateTime::now()->subHour(2) // @todo use session timeout from config
169                 ),
170             )),
171             new Tinebase_Model_Pagination(array(
172                 'sort'  => 'li',
173                 'dir'   => 'DESC',
174                 'limit' => 1
175             ))
176         )->getFirstRecord();
177         
178         if (!$previousAccessLog) {
179             throw new Tinebase_Exception_NotFound('previous access log entry not found');
180         }
181         
182         return $previousAccessLog;
183     }
184     
185     /**
186      * add logout entry to the access log
187      *
188      * @param string $_sessionId the session id
189      * @param string $_ipAddress the ip address the user connects from
190      * @return null|Tinebase_Model_AccessLog
191      */
192     public function setLogout()
193     {
194         $sessionId = Tinebase_Core::getSessionId();
195
196         try {
197             if (Tinebase_Core::isRegistered(Tinebase_Core::USERACCESSLOG)) {
198                 $loginRecord = Tinebase_Core::get(Tinebase_Core::USERACCESSLOG);
199             } else {
200                 $loginRecord = $this->_backend->getByProperty($sessionId, 'sessionid');
201             }
202         } catch (Tinebase_Exception_NotFound $tenf) {
203             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
204                 . ' Could not find access log login record for session id ' . $sessionId);
205             return null;
206         }
207         
208         $loginRecord->lo = Tinebase_DateTime::now();
209         
210         // call update of backend direct to save overhead of $this->update()
211         return $this->_backend->update($loginRecord);
212     }
213
214     /**
215      * clear access log table
216      * - if $date param is omitted, the last 60 days of access log are kept, the rest will be removed
217      * 
218      * @param Tinebase_DateTime $date
219      * @return integer deleted rows
220      * 
221      * @todo use $this->deleteByFilter($_filter)? might be slow for huge access_logs
222      */
223     public function clearTable($date = NULL)
224     {
225         $date = ($date instanceof Tinebase_DateTime) ? $date : Tinebase_DateTime::now()->subDay(60);
226         
227         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
228             . ' Removing all access log entries before ' . $date->toString());
229         
230         $db = $this->_backend->getAdapter();
231         $where = array(
232             $db->quoteInto($db->quoteIdentifier('li') . ' < ?', $date->toString())
233         );
234         $deletedRows = $db->delete($this->_backend->getTablePrefix() . $this->_backend->getTableName(), $where);
235         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
236             . ' Removed ' . $deletedRows . ' rows.');
237         
238         return $deletedRows;
239     }
240
241
242     /**
243      * return accessLog instance
244      *
245      * @param string $loginName
246      * @param Zend_Auth_Result $authResult
247      * @param Zend_Controller_Request_Abstract $request
248      * @param string $clientIdString
249      * @return Tinebase_Model_AccessLog
250      */
251     public function getAccessLogEntry($loginName, Zend_Auth_Result $authResult, \Zend\Http\Request $request, $clientIdString)
252     {
253         if ($header = $request->getHeaders('USER-AGENT')) {
254             $userAgent = substr($header->getFieldValue(), 0, 255);
255         } else {
256             $userAgent = 'unknown';
257         }
258
259         $accessLog = new Tinebase_Model_AccessLog(array(
260             'ip'         => $request->getServer('REMOTE_ADDR'),
261             'li'         => Tinebase_DateTime::now(),
262             'result'     => $authResult->getCode(),
263             'clienttype' => $clientIdString,
264             'login_name' => $loginName ? $loginName : $authResult->getIdentity(),
265             'user_agent' => $userAgent
266         ), true);
267
268         return $accessLog;
269     }
270
271     /**
272      * set session id for current request in accesslog
273      *
274      * @param Tinebase_Model_AccessLog $accessLog
275      */
276     public function setSessionId(Tinebase_Model_AccessLog $accessLog)
277     {
278         if (in_array($accessLog->clienttype, array(Tinebase_Server_WebDAV::REQUEST_TYPE, ActiveSync_Server_Http::REQUEST_TYPE))) {
279             try {
280                 $previousAccessLog = Tinebase_AccessLog::getInstance()->getPreviousAccessLog($accessLog);
281                 $accessLog->merge($previousAccessLog);
282             } catch (Tinebase_Exception_NotFound $tenf) {
283                 // ignore
284             }
285         }
286
287         if (empty($accessLog->sessionid)) {
288             $accessLog->sessionid = Tinebase_Core::getSessionId();
289         } else {
290             Tinebase_Core::set(Tinebase_Core::SESSIONID, $accessLog->sessionid);
291         }
292     }
293 }