0013268: show user report (CLI)
authorPhilipp Schüle <p.schuele@metaways.de>
Tue, 27 Jun 2017 08:04:09 +0000 (10:04 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Fri, 30 Jun 2017 18:19:45 +0000 (20:19 +0200)
https://forge.tine20.org/view.php?id=13268

Change-Id: Ibaf5ca6c3dd5bd8108f1b453b2115ccc1eefc155
Reviewed-on: http://gerrit.tine20.com/customers/4985
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/Frontend/CliTest.php
tine20/Tinebase/AccessLog.php
tine20/Tinebase/Frontend/Cli.php
tine20/Tinebase/User/Abstract.php

index c747252..a8ceea3 100644 (file)
@@ -524,4 +524,49 @@ class Tinebase_Frontend_CliTest extends TestCase
         }
         $this->assertEquals($realDataCustomFields, $sum, 'customfields not completely cleaned');
     }
+
+
+    /**
+     * testUserReport
+     *
+     * TODO maybe set locale to EN to ease string testing
+     */
+    public function testUserReport()
+    {
+        ob_start();
+        $result = $this->_cli->userReport();
+        $out = ob_get_clean();
+        $this->assertEquals(0, $result);
+
+        preg_match('/Number of users \(total\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches), $out);
+        $this->assertGreaterThanOrEqual(1, $matches[1], 'at least unittest user should be in users: ' . $out);
+
+        preg_match('/Number of users \(enabled\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(1, $matches[1], 'at least unittest user should be in users');
+
+        preg_match('/Number of users \(disabled\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(0, $matches[1]);
+
+        preg_match('/Number of users \(blocked\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(0, $matches[1]);
+
+        preg_match('/Number of users \(expired\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(0, $matches[1]);
+
+        preg_match('/Number of distinct users \(lastmonth\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(1, $matches[1]);
+
+        preg_match('/Number of distinct users \(last 3 months\): (\d+)/', $out, $matches);
+        $this->assertGreaterThan(1, count($matches));
+        $this->assertGreaterThanOrEqual(1, $matches[1]);
+
+        $this->assertContains(Tinebase_Core::getUser()->accountLoginName, $out, 'unittest login name should be found');
+        $this->assertContains('Unit Test Client', $out, 'unittest should have a user agent');
+    }
 }
index 84cc52f..75d372f 100644 (file)
@@ -103,26 +103,62 @@ class Tinebase_AccessLog extends Tinebase_Controller_Record_Abstract
     protected function _tooManyUserAgents($_user, $numberOfAllowedUserAgents = 3)
     {
         $result = false;
+
+        $userAgents = $this->_getUserAgentsByInterval($_user);
+        if (count($userAgents) > $numberOfAllowedUserAgents) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' More than ' . $numberOfAllowedUserAgents . ' different UserAgents? we don\'t trust you!');
+            $result = true;
+        }
+        return $result;
+    }
+
+    /**
+     * @param        $_user
+     * @param string $_unit
+     * @param int    $_interval
+     * @return array
+     * @throws Tinebase_Exception_Backend_Database
+     *
+     * TODO move to backend
+     */
+    protected function _getUserAgentsByInterval($_user, $_unit = 'HOUR', $_interval = 1, $_onlyInvalidLogins = true)
+    {
         $db = $this->_backend->getAdapter();
         $dbCommand = Tinebase_Backend_Sql_Command::factory($db);
         $select = $db->select()
             ->distinct(true)
             ->from($this->_backend->getTablePrefix() . $this->_backend->getTableName(), 'user_agent')
             ->where( $db->quoteIdentifier('account_id') . ' = ?', $_user->getId() )
-            ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval('HOUR', '1'))
-            ->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT)
-            ->limit(10);
+            ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval($_unit, $_interval))
+            ->limit(20);
 
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
+        if ($_onlyInvalidLogins) {
+            $select->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT);
+        }
 
         $stmt = $db->query($select);
+        $result = $stmt->fetchAll();
+        $stmt->closeCursor();
 
-        if ($stmt->columnCount() > $numberOfAllowedUserAgents) {
-            if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-                . ' More than ' . $numberOfAllowedUserAgents . ' different UserAgents? we don\'t trust you!');
-            $result = true;
+        return $result;
+    }
+
+    /**
+     * @param     $user
+     * @param int $lastMonths
+     * @return array of user clients
+     */
+    public function getUserClients($user, $lastMonths = 1)
+    {
+        $userAgents = $this->_getUserAgentsByInterval($user, 'MONTH', $lastMonths, /* $_onlyInvalidLogins */ false);
+        $result = array();
+        foreach ($userAgents as $row) {
+            // TODO maybe _getUserAgentsByInterval could already do this ...
+            if (isset($row['user_agent']) && $row['user_agent']) {
+                $result[] = $row['user_agent'];
+            }
         }
-        $stmt->closeCursor();
         return $result;
     }
 
index a5adb9a..1d52293 100644 (file)
@@ -350,10 +350,10 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
         }
         
         $userController = Tinebase_User::getInstance();
-        
+
         // deactivate user plugins (like postfix/dovecot email backends) for async job user
         $userController->unregisterAllPlugins();
-        
+
         try {
             $cronuser = $userController->getFullUserByLoginName($_opts->username);
         } catch (Tinebase_Exception_NotFound $tenf) {
@@ -793,11 +793,11 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
 
     /**
      * add new customfield config
-     * 
+     *
      * needs args like this:
      * application="Addressbook" name="datefield" label="Date" model="Addressbook_Model_Contact" type="datefield"
-     * @see Tinebase_Model_CustomField_Config for full list 
-     * 
+     * @see Tinebase_Model_CustomField_Config for full list
+     *
      * @param $_opts
      * @return boolean success
      */
@@ -1142,7 +1142,7 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
 
         return 0;
     }
-    
+
     /**
      * repair a table
      * 
@@ -1325,7 +1325,7 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
     public function repairContainerOwner()
     {
         if (! $this->_checkAdminRight()) {
-            return -1;
+            return 2;
         }
 
         $this->_addOutputLogWriter(6);
@@ -1333,4 +1333,70 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
 
         return 0;
     }
+
+    /**
+     * show user report (number of enabled, disabled, ... users)
+     *
+     * TODO add system user count
+     * TODO use twig?
+     */
+    public function userReport()
+    {
+        if (! $this->_checkAdminRight()) {
+            return 2;
+        }
+
+        $translation = Tinebase_Translation::getTranslation('Tinebase');
+
+        $userStatus = array(
+            'total' => array(),
+            Tinebase_Model_User::ACCOUNT_STATUS_ENABLED => array(/* 'showUserNames' => true, 'showClients' => true */),
+            Tinebase_Model_User::ACCOUNT_STATUS_DISABLED => array(),
+            Tinebase_Model_User::ACCOUNT_STATUS_BLOCKED => array(),
+            Tinebase_Model_User::ACCOUNT_STATUS_EXPIRED => array(),
+            //'system' => array(),
+            'lastmonth' => array('lastMonths' => 1, 'showUserNames' => true, 'showClients' => true),
+            'last 3 months' => array('lastMonths' => 3),
+        );
+
+        foreach ($userStatus as $status => $options) {
+            switch ($status) {
+                case 'lastmonth':
+                case 'last 3 months':
+                    $userCount = Tinebase_User::getInstance()->getActiveUserCount($options['lastMonths']);
+                    $text = $translation->_("Number of distinct users") . " (" . $status . "): " . $userCount . "\n";
+                    break;
+                case 'system':
+                    $text = "TODO add me\n";
+                    break;
+                default:
+                    $userCount = Tinebase_User::getInstance()->getUserCount($status);
+                    $text = $translation->_("Number of users") . " (" . $status . "): " . $userCount . "\n";
+            }
+            echo $text;
+
+            if (isset($options['showUserNames']) && $options['showUserNames']
+                && in_array($status, array('lastmonth', 'last 3 months'))
+                && isset($options['lastMonths'])
+            ) {
+                // TODO allow this for other status
+                echo $translation->_("  User Accounts:\n");
+                $userIds = Tinebase_User::getInstance()->getActiveUserIds($options['lastMonths']);
+                foreach ($userIds as $userId) {
+                    $user = Tinebase_User::getInstance()->getUserByProperty('accountId', $userId, 'Tinebase_Model_FullUser');
+                    echo "  * " . $user->accountLoginName . ' / ' . $user->accountDisplayName . "\n";
+                    if (isset($options['showClients']) && $options['showClients']) {
+                        $userClients = Tinebase_AccessLog::getInstance()->getUserClients($user, $options['lastMonths']);
+                        echo "    Clients: \n";
+                        foreach ($userClients as $client) {
+                            echo "     - $client\n";
+                        }
+                    }
+                }
+            }
+            echo "\n";
+        }
+
+        return 0;
+    }
 }
index 4bcc541..55193fe 100644 (file)
@@ -571,9 +571,22 @@ abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
     /**
      * returns active users
      *
+     * @params integer $lastMonths
      * @return int
      */
-    public function getActiveUserCount()
+    public function getActiveUserCount($lastMonths = 1)
+    {
+        $ids = $this->getActiveUserIds($lastMonths);
+        return count($ids);
+    }
+
+    /**
+     * returns active users
+     *
+     * @params integer $lastMonths
+     * @return array of user ids
+     */
+    public function getActiveUserIds($lastMonths = 1)
     {
         $backend = new Tinebase_Backend_Sql(array(
             'modelName' => 'Tinebase_Model_User',
@@ -581,13 +594,40 @@ abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
             'modlogActive' => true,
         ));
 
-        // TODO allow to set this as param
-        $afterDate = Tinebase_DateTime::now()->subMonth(1);
+        $afterDate = Tinebase_DateTime::now()->subMonth($lastMonths);
         $filter = new Tinebase_Model_FullUserFilter(array(
             array('field' => 'last_login', 'operator' => 'after', 'value' => $afterDate),
             array('field' => 'status', 'operator' => 'equals', 'value' => Tinebase_Model_User::ACCOUNT_STATUS_ENABLED),
         ));
 
+        return $backend->search($filter, null, /* $_cols`*/ Tinebase_Backend_Sql_Abstract::IDCOL);
+    }
+
+    /**
+     * returns users by status
+     *
+     * @param string $status
+     * @return int
+     */
+    public function getUserCount($status = null)
+    {
+        $backend = new Tinebase_Backend_Sql(array(
+            'modelName' => 'Tinebase_Model_User',
+            'tableName' => 'accounts',
+            'modlogActive' => true,
+        ));
+
+        $statusFilter = in_array($status, array(
+                Tinebase_Model_User::ACCOUNT_STATUS_ENABLED,
+                Tinebase_Model_User::ACCOUNT_STATUS_DISABLED,
+                Tinebase_Model_User::ACCOUNT_STATUS_BLOCKED,
+                Tinebase_Model_User::ACCOUNT_STATUS_EXPIRED,
+            ))
+            ? array('field' => 'status', 'operator' => 'equals', 'value' => $status)
+            : array();
+
+        $filter = new Tinebase_Model_FullUserFilter(array($statusFilter));
+
         return $backend->searchCount($filter);
     }