0003746: add maintenance mode
authorCornelius Weiß <c.weiss@metaways.de>
Fri, 11 Sep 2015 08:31:12 +0000 (10:31 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 18 Nov 2015 11:33:29 +0000 (12:33 +0100)
* new config option maintenanceMode
* new right maintenance
* deny logins in maintenance mode if user has no maintenance right
* invalidate session when maintenance mode got enabled
* updates syncroton/syncroton to 1.1.2 (needed for ActiveSync session invalidation)

https://forge.tine20.org/view.php?id=3746

Change-Id: Ib13b6fad738a70257f205e21465639cb88d3fd8d
Reviewed-on: http://gerrit.tine20.com/customers/2209
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
13 files changed:
tests/tine20/Tinebase/ControllerTest.php
tine20/ActiveSync/Controller.php
tine20/Tinebase/Acl/Rights.php
tine20/Tinebase/Config.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Core.php
tine20/Tinebase/Exception/MaintenanceMode.php [new file with mode: 0644]
tine20/Tinebase/Session.php
tine20/Tinebase/Session/Validator/MaintenanceMode.php [new file with mode: 0644]
tine20/Tinebase/js/LoginPanel.js
tine20/composer.json
tine20/composer.lock
tine20/config.inc.php.dist

index e211ece..7e01d0b 100644 (file)
@@ -59,6 +59,7 @@ class Tinebase_ControllerTest extends PHPUnit_Framework_TestCase
      */
     protected function tearDown()
     {
+        Tinebase_Config::getInstance()->maintenanceMode = 0;
     }
     
     /**
@@ -86,6 +87,21 @@ class Tinebase_ControllerTest extends PHPUnit_Framework_TestCase
 //    }
 
     /**
+     * testMaintenanceModeLoginFail
+     */
+    public function testMaintenanceModeLoginFail()
+    {
+        Tinebase_Config::getInstance()->maintenanceMode = 1;
+        $this->setExpectedException('Tinebase_Exception_MaintenanceMode');
+
+        $this->_instance->login(
+            'sclever',
+            Tinebase_Helper::array_value('password', TestServer::getInstance()->getTestCredentials()),
+            new \Zend\Http\PhpEnvironment\Request()
+        );
+    }
+
+    /**
      * testCleanupCache
      */
     public function testCleanupCache()
index bbdd6d0..8656613 100644 (file)
@@ -120,6 +120,9 @@ class ActiveSync_Controller extends Tinebase_Controller_Abstract
         Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND,    new Syncroton_Backend_SyncState(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
         Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new Syncroton_Backend_Content(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
         Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND,       new Syncroton_Backend_Policy(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
-        Syncroton_Registry::set('loggerBackend',       Tinebase_Core::getLogger());
+        Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND,       Tinebase_Core::getLogger());
+        Syncroton_Registry::set(Syncroton_Registry::SESSION_VALIDATOR,   function() {
+            return ! Tinebase_Core::inMaintenanceMode();
+        });
     }
 }
index d200a65..3714472 100644 (file)
@@ -49,6 +49,12 @@ class Tinebase_Acl_Rights extends Tinebase_Acl_Rights_Abstract
      * @staticvar string
      */
     const MANAGE_OWN_STATE = 'manage_own_state';
+
+    /**
+     * the right to use the installation in maintenance mode
+     * @staticvar string
+     */
+    const MAINTENANCE = 'maintenance';
     
     /**
      * account type anyone
@@ -123,6 +129,7 @@ class Tinebase_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 self::CHECK_VERSION,
                 self::MANAGE_OWN_PROFILE,
                 self::MANAGE_OWN_STATE,
+                self::MAINTENANCE,
             );
         } else {
             $addRights = array();
@@ -159,6 +166,10 @@ class Tinebase_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 'text'          => $translate->_('Manage own client state'),
                 'description'   => $translate->_('The right to manage the own client state.'),
             ),
+            self::MAINTENANCE       => array(
+                'text'          => $translate->_('Maintenance'),
+                'description'   => $translate->_('The right to use the installation in maintenance mode.'),
+            ),
         );
         
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
index 97bc948..e5628e4 100644 (file)
@@ -322,6 +322,13 @@ class Tinebase_Config extends Tinebase_Config_Abstract
     const CONFD_FOLDER = 'confdfolder';
 
     /**
+     * maintenance mode
+     *
+     * @var bool
+     */
+    const MAINTENANCE_MODE = 'maintenanceMode';
+
+    /**
      * (non-PHPdoc)
      * @see tine20/Tinebase/Config/Definition::$_properties
      */
@@ -734,6 +741,17 @@ class Tinebase_Config extends Tinebase_Config_Abstract
             'setByAdminModule'      => FALSE,
             'setBySetupModule'      => FALSE,
         ),
+        self::MAINTENANCE_MODE => array(
+            //_('Maintenance mode enabled')
+            'label'                 => 'Maintenance mode enabled',
+            //_('Folder for additional config files (conf.d) - NOTE: this is only used if set in config.inc.php!')
+            'description'           => 'Installation is in maintenance mode. With this only users having the maintenance right can login',
+            'type'                  => 'bool',
+            'default'               => '',
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => TRUE,
+            'setBySetupModule'      => TRUE,
+        ),
     );
     
     /**
index f7084fb..9f26de1 100644 (file)
@@ -75,6 +75,7 @@ class Tinebase_Controller extends Tinebase_Controller_Event
      * @param   string                           $clientIdString
      *
      * @return  bool
+     * @throws  Tinebase_Exception_MaintenanceMode
      *
      * TODO what happened to the $securitycode parameter?
      *  ->  @param   string                           $securitycode   the security code(captcha)
@@ -93,7 +94,13 @@ class Tinebase_Controller extends Tinebase_Controller_Event
         
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
             __METHOD__ . '::' . __LINE__ . " Login with username {$accessLog->login_name} from {$accessLog->ip} succeeded.");
-        
+
+        if (Tinebase_Core::inMaintenanceMode()) {
+            if (! $user->hasRight('Tinebase', Tinebase_Acl_Rights::MAINTENANCE)) {
+                throw new Tinebase_Exception_MaintenanceMode();
+            }
+        }
+
         $this->_setSessionId($accessLog);
         
         $this->initUser($user);
@@ -222,6 +229,8 @@ class Tinebase_Controller extends Tinebase_Controller_Event
     {
         // FIXME 0010508: Session_Validator_AccountStatus causes problems
         //Tinebase_Session::registerValidatorAccountStatus();
+
+        Tinebase_Session::registerValidatorMaintenanceMode();
         
         if (Tinebase_Config::getInstance()->get(Tinebase_Config::SESSIONUSERAGENTVALIDATION, TRUE)) {
             Tinebase_Session::registerValidatorHttpUserAgent();
index 1e7c9a5..2913822 100644 (file)
@@ -1626,4 +1626,15 @@ class Tinebase_Core
     {
         return shell_exec(escapeshellcmd($cmd));
     }
+
+    /**
+     * returns true if installation is in maintenance mode
+     *
+     * @return bool
+     */
+    public static function inMaintenanceMode()
+    {
+        $config = self::getConfig();
+        return !! $config->maintenanceMode;
+    }
 }
diff --git a/tine20/Tinebase/Exception/MaintenanceMode.php b/tine20/Tinebase/Exception/MaintenanceMode.php
new file mode 100644 (file)
index 0000000..8b24132
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Exception
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Cornelius Weiß <c.weiss@metaways.de>
+ *
+ */
+
+/**
+ * Tinebase_Exception_MaintenanceMode
+ *
+ * @package     Tinebase
+ * @subpackage  Exception
+ */
+class Tinebase_Exception_MaintenanceMode extends Tinebase_Exception
+{
+    public function __construct($_message='Installation is in maintenance mode. Please try again later', $_code=503) {
+        parent::__construct($_message, $_code);
+    }
+}
index 4d890d0..501a8f9 100644 (file)
@@ -46,4 +46,9 @@ class Tinebase_Session extends Tinebase_Session_Abstract
     {
         Zend_Session::registerValidator(new Zend_Session_Validator_IpAddress());
     }
+
+    public static function registerValidatorMaintenanceMode()
+    {
+        Zend_Session::registerValidator(new Tinebase_Session_Validator_MaintenanceMode());
+    }
 }
diff --git a/tine20/Tinebase/Session/Validator/MaintenanceMode.php b/tine20/Tinebase/Session/Validator/MaintenanceMode.php
new file mode 100644 (file)
index 0000000..338ad66
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Tinebase
+ * @subpackage  Session
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ */
+
+/**
+ * Tinebase_Session_Validator_MaintenanceMode
+ *
+ * @package    Tinebase
+ * @subpackage Session
+ */
+class Tinebase_Session_Validator_MaintenanceMode extends Zend_Session_Validator_Abstract
+{
+
+    /**
+     * @return void
+     */
+    public function setup()
+    {
+        $this->setValidData(Tinebase_Core::getUser()->accountId);
+    }
+
+    /**
+     * validate if user is allowed to use the software in maintenance mode
+     *
+     * @return bool
+     */
+    public function validate()
+    {
+        if (Tinebase_Core::inMaintenanceMode()) {
+            $currentAccount = Tinebase_User::getInstance()->getFullUserById($this->getValidData());
+            if (!$currentAccount->hasRight('Tinebase', Tinebase_Acl_Rights::MAINTENANCE)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
index b742614..0752297 100644 (file)
@@ -458,7 +458,7 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
             
         if (form.isValid()) {
             Ext.MessageBox.wait(_('Logging you in...'), _('Please wait'));
-            
+
             Ext.Ajax.request({
                 scope: this,
                 params : {
@@ -468,39 +468,31 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                     securitycode: values.securitycode
                 },
                 timeout: 60000, // 1 minute
-                callback: function (request, httpStatus, response) {
+                success:function(response) {
                     var responseData = Ext.util.JSON.decode(response.responseText);
                     if (responseData.success === true) {
                         Ext.MessageBox.wait(String.format(_('Login successful. Loading {0}...'), Tine.title), _('Please wait!'));
                         window.document.title = this.originalTitle;
                         this.onLogin.call(this.scope);
                     } else {
-                        if (responseData.data && responseData.data.code === 510) {
-                            // NOTE: when communication is lost, we can't create a nice ext window.
-                            (function() {
-                                Ext.MessageBox.hide();
-                                alert(_('Connection lost, please check your network!'));
-                            }).defer(1000);
-                        } else {
-                            var modSsl = Tine.Tinebase.registry.get('modSsl');
-                            var resultMsg = modSsl ? _('There was an error verifying your certificate!!!') : 
-                                _('Your username and/or your password are wrong!!!');
-                            Ext.MessageBox.show({
-                                title: _('Login failure'),
-                                msg: resultMsg,
-                                buttons: Ext.MessageBox.OK,
-                                icon: Ext.MessageBox.ERROR,
-                                fn: function () {
-                                    this.getLoginPanel().getForm().findField('password').focus(true);
-                                    if(document.getElementById('useCaptcha')) {
-                                        if(typeof responseData.c1 != 'undefined') {
-                                            document.getElementById('imgCaptcha').src = 'data:image/png;base64,' + responseData.c1;
-                                            document.getElementById('contImgCaptcha').style.visibility = 'visible';  
-                                        }
+                        var modSsl = Tine.Tinebase.registry.get('modSsl');
+                        var resultMsg = modSsl ? _('There was an error verifying your certificate!!!') :
+                            _('Your username and/or your password are wrong!!!');
+                        Ext.MessageBox.show({
+                            title: _('Login failure'),
+                            msg: resultMsg,
+                            buttons: Ext.MessageBox.OK,
+                            icon: Ext.MessageBox.ERROR,
+                            fn: function () {
+                                this.getLoginPanel().getForm().findField('password').focus(true);
+                                if(document.getElementById('useCaptcha')) {
+                                    if(typeof responseData.c1 != 'undefined') {
+                                        document.getElementById('imgCaptcha').src = 'data:image/png;base64,' + responseData.c1;
+                                        document.getElementById('contImgCaptcha').style.visibility = 'visible';
                                     }
-                                }.createDelegate(this)
-                            });
-                        }
+                                }
+                            }.createDelegate(this)
+                        });
                     }
                 }
             });
index f388a8f..40ec003 100644 (file)
@@ -17,6 +17,9 @@
     }, {
         "type": "git",
         "url": "https://github.com/lkneschke/Component_ZendHttp"
+    }, {
+        "type": "git",
+        "url": "http://git.syncroton.org/Syncroton"
     }],
     "require": {
         "syncroton/syncroton": "1.1.*",
index 8f966b8..f54223f 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "2cccddae39ae398ee1370cbc6264cacf",
+    "hash": "7f598a1ec2745d0206ba93eba4bca97a",
     "packages": [
         {
             "name": "codeplex/phpexcel",
             "authors": [
                 {
                     "name": "Evert Pot",
-                    "email": "evert@rooftopsolutions.nl",
-                    "homepage": "http://www.rooftopsolutions.nl/",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
                     "role": "Developer"
                 }
             ],
         },
         {
             "name": "syncroton/syncroton",
-            "version": "1.1.1",
+            "version": "1.1.2",
             "source": {
                 "type": "git",
                 "url": "http://git.syncroton.org/Syncroton",
-                "reference": "31d4fc56c65eaab4edca3af34dd803dba4c0b9f2"
+                "reference": "c3b115fe17c6b4b9a5b9bef8f7dc3c281a8d4dcf"
             },
             "require": {
                 "php": ">=5.3.0",
                     ]
                 }
             },
-            "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "LGPL-3"
             ],
             "description": "Library to sync mobile phones",
             "homepage": "http://www.syncroton.org",
-            "time": "2015-05-20 13:08:49"
+            "time": "2015-09-22 09:21:43"
         },
         {
             "name": "zendframework/zend-escaper",
             "target-dir": "Symfony/Component/Config",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Config.git",
+                "url": "https://github.com/symfony/config.git",
                 "reference": "1ced3d6c88b22df8cd1fe5209dbd6a89df362a29"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Config/zipball/1ced3d6c88b22df8cd1fe5209dbd6a89df362a29",
+                "url": "https://api.github.com/repos/symfony/config/zipball/1ced3d6c88b22df8cd1fe5209dbd6a89df362a29",
                 "reference": "1ced3d6c88b22df8cd1fe5209dbd6a89df362a29",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Console",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Console.git",
+                "url": "https://github.com/symfony/console.git",
                 "reference": "f880062d56edefb25b36f2defa65aafe65959dc7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Console/zipball/f880062d56edefb25b36f2defa65aafe65959dc7",
+                "url": "https://api.github.com/repos/symfony/console/zipball/f880062d56edefb25b36f2defa65aafe65959dc7",
                 "reference": "f880062d56edefb25b36f2defa65aafe65959dc7",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/EventDispatcher",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/EventDispatcher.git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
                 "reference": "7fc72a7a346a1887d3968cc1ce5642a15cd182e9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/7fc72a7a346a1887d3968cc1ce5642a15cd182e9",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fc72a7a346a1887d3968cc1ce5642a15cd182e9",
                 "reference": "7fc72a7a346a1887d3968cc1ce5642a15cd182e9",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Filesystem",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Filesystem.git",
+                "url": "https://github.com/symfony/filesystem.git",
                 "reference": "2b8995042086c5552c94d33b5553c492e9cfc00e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Filesystem/zipball/2b8995042086c5552c94d33b5553c492e9cfc00e",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/2b8995042086c5552c94d33b5553c492e9cfc00e",
                 "reference": "2b8995042086c5552c94d33b5553c492e9cfc00e",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Finder",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Finder.git",
+                "url": "https://github.com/symfony/finder.git",
                 "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Finder/zipball/a175521f680b178e63c5d0ab87c6b046c0990c3f",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/a175521f680b178e63c5d0ab87c6b046c0990c3f",
                 "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Process",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Process.git",
+                "url": "https://github.com/symfony/process.git",
                 "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Process/zipball/81191e354fe9dad0451036ccf0fdf283649d3f1e",
+                "url": "https://api.github.com/repos/symfony/process/zipball/81191e354fe9dad0451036ccf0fdf283649d3f1e",
                 "reference": "81191e354fe9dad0451036ccf0fdf283649d3f1e",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Stopwatch",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Stopwatch.git",
+                "url": "https://github.com/symfony/stopwatch.git",
                 "reference": "1f951fa881d2e661525e81ee0afc97261ad43459"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/1f951fa881d2e661525e81ee0afc97261ad43459",
+                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1f951fa881d2e661525e81ee0afc97261ad43459",
                 "reference": "1f951fa881d2e661525e81ee0afc97261ad43459",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Translation",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Translation.git",
+                "url": "https://github.com/symfony/translation.git",
                 "reference": "6aedcff5ea623316dbf2112b78f2f63a257aab01"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Translation/zipball/6aedcff5ea623316dbf2112b78f2f63a257aab01",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/6aedcff5ea623316dbf2112b78f2f63a257aab01",
                 "reference": "6aedcff5ea623316dbf2112b78f2f63a257aab01",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Validator",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Validator.git",
+                "url": "https://github.com/symfony/validator.git",
                 "reference": "3d659f721f18425034bab0998c82de10d091ce7a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Validator/zipball/3d659f721f18425034bab0998c82de10d091ce7a",
+                "url": "https://api.github.com/repos/symfony/validator/zipball/3d659f721f18425034bab0998c82de10d091ce7a",
                 "reference": "3d659f721f18425034bab0998c82de10d091ce7a",
                 "shasum": ""
             },
             "target-dir": "Symfony/Component/Yaml",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/Yaml.git",
+                "url": "https://github.com/symfony/yaml.git",
                 "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/Yaml/zipball/6bb881b948368482e1abf1a75c08bcf88a1c5fc3",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/6bb881b948368482e1abf1a75c08bcf88a1c5fc3",
                 "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3",
                 "shasum": ""
             },
index d087c1c..02996cf 100644 (file)
@@ -6,8 +6,12 @@
 
 // minimal configuration
 return array(
+    // in maintenanceMode only users of the defaultAdminGroup can login and operate
+    'maintenanceMode'   => false,
+
     // set 'count' equal zero to disable captcha, or set to number of invalid logins before request captcha.
-    'captcha' => array('count'=>0), 
+    'captcha' => array('count'=>0),
+
     'database' => array(
         'host'          => 'ENTER DATABASE HOSTNAME',
         'dbname'        => 'ENTER DATABASE NAME',
@@ -16,6 +20,7 @@ return array(
         'adapter'       => 'pdo_mysql',
         'tableprefix'   => 'tine20_',
     ),
+
     'setupuser' => array(
         'username'      => 'SETUP USERNAME',
         'password'      => 'SETUP PASSWORD'